excel2moodle 0.5.1__py3-none-any.whl → 0.5.2__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/core/dataStructure.py +3 -3
- excel2moodle/core/question.py +1 -1
- excel2moodle/question_types/cloze.py +217 -82
- excel2moodle/question_types/nfm.py +5 -4
- excel2moodle/ui/appUi.py +18 -22
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.5.2.dist-info}/METADATA +41 -1
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.5.2.dist-info}/RECORD +11 -11
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.5.2.dist-info}/WHEEL +0 -0
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.5.2.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.5.2.dist-info}/top_level.txt +0 -0
@@ -131,7 +131,7 @@ class QuestionDB:
|
|
131
131
|
if Tags.IMPORTMODULE in self.settings:
|
132
132
|
logger.warning(
|
133
133
|
"Appending: %s to sys.path. All names defined by it will be usable",
|
134
|
-
sheetPath,
|
134
|
+
sheetPath.parent,
|
135
135
|
)
|
136
136
|
sys.path.append(str(sheetPath.parent))
|
137
137
|
if Tags.PICTURESUBFOLDER not in self.settings:
|
@@ -286,7 +286,7 @@ class QuestionDB:
|
|
286
286
|
self.signals.categoryQuestionsReady.emit(category)
|
287
287
|
|
288
288
|
@classmethod
|
289
|
-
def setupAndParseQuestion(cls, category: Category, qNumber: int) -> Question
|
289
|
+
def setupAndParseQuestion(cls, category: Category, qNumber: int) -> Question:
|
290
290
|
"""Check if the Question Data is valid. Then parse it.
|
291
291
|
|
292
292
|
The Question data is accessed from `category.dataframe` via its number
|
@@ -322,7 +322,7 @@ class QuestionDB:
|
|
322
322
|
question = QuestionTypeMapping[qtype].create(category, validData)
|
323
323
|
if question.isParsed:
|
324
324
|
locallogger.info("Question already parsed")
|
325
|
-
return
|
325
|
+
return question
|
326
326
|
if isinstance(question, NFQuestion):
|
327
327
|
cls.nfParser.setup(question)
|
328
328
|
locallogger.debug("setup a new NF parser ")
|
excel2moodle/core/question.py
CHANGED
@@ -161,7 +161,7 @@ class Question:
|
|
161
161
|
def assemble(self, variant=0) -> None:
|
162
162
|
"""Assemble the question to the valid xml Tree."""
|
163
163
|
mainText = self._getTextElement()
|
164
|
-
self.logger.
|
164
|
+
self.logger.info("Starting assembly variant: %s", variant)
|
165
165
|
self._assembleAnswer(variant=variant)
|
166
166
|
textParts = self._assembleText(variant=variant)
|
167
167
|
if hasattr(self, "picture") and self.picture.ready:
|
@@ -1,11 +1,13 @@
|
|
1
|
-
"""Implementation of
|
1
|
+
"""Implementation of tde cloze question type.
|
2
2
|
|
3
3
|
This question type is like the NFM but supports multiple fields of answers.
|
4
4
|
All Answers are calculated off an equation using the same variables.
|
5
5
|
"""
|
6
6
|
|
7
|
+
import logging
|
7
8
|
import math
|
8
9
|
import re
|
10
|
+
from typing import Literal, overload
|
9
11
|
|
10
12
|
import lxml.etree as ET
|
11
13
|
|
@@ -18,43 +20,143 @@ from excel2moodle.core.question import ParametricQuestion
|
|
18
20
|
from excel2moodle.core.settings import Tags
|
19
21
|
from excel2moodle.question_types.nfm import NFMQuestionParser
|
20
22
|
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
class ClozePart:
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
question: ParametricQuestion,
|
30
|
+
text: list[str],
|
31
|
+
) -> None:
|
32
|
+
self.question = question
|
33
|
+
self.text: list[ET.Element] = self._setupText(text)
|
34
|
+
if not self.text:
|
35
|
+
msg = f"Answer part for cloze question {self.question.id} is invalid without partText"
|
36
|
+
raise ValueError(msg)
|
37
|
+
|
38
|
+
@property
|
39
|
+
def points(self) -> float:
|
40
|
+
if hasattr(self, "_points"):
|
41
|
+
return self._points
|
42
|
+
return 0.0
|
43
|
+
self.question.logger.error("Invalid call to points of unparsed cloze part")
|
44
|
+
return 0.0
|
45
|
+
|
46
|
+
@points.setter
|
47
|
+
def points(self, points: float) -> None:
|
48
|
+
self._points = points if points > 0 else 0.0
|
49
|
+
|
50
|
+
@property
|
51
|
+
def typ(self) -> Literal["MC", "NFM"] | None:
|
52
|
+
if hasattr(self, "_typ"):
|
53
|
+
return self._typ
|
54
|
+
return None
|
55
|
+
|
56
|
+
@property
|
57
|
+
def mcAnswerString(self) -> str:
|
58
|
+
if hasattr(self, "_mcAnswer"):
|
59
|
+
return self._mcAnswer
|
60
|
+
msg = "No MC Answer was set"
|
61
|
+
raise ValueError(msg)
|
62
|
+
|
63
|
+
@mcAnswerString.setter
|
64
|
+
def mcAnswerString(self, answerString: str) -> None:
|
65
|
+
self._mcAnswer: str = answerString
|
66
|
+
|
67
|
+
def _setupText(self, text: list[str]) -> ET.Element:
|
68
|
+
textList: list[ET.Element] = []
|
69
|
+
for t in text:
|
70
|
+
textList.append(TextElements.PLEFT.create())
|
71
|
+
textList[-1].text = t
|
72
|
+
return textList
|
73
|
+
|
74
|
+
def setAnswer(
|
75
|
+
self,
|
76
|
+
equation: str | None = None,
|
77
|
+
trueAns: list[str] | None = None,
|
78
|
+
falseAns: list[str] | None = None,
|
79
|
+
) -> bool:
|
80
|
+
if falseAns is not None:
|
81
|
+
self.falseAnswers: list[str] = falseAns
|
82
|
+
if trueAns is not None:
|
83
|
+
self.trueAnswers: list[str] = trueAns
|
84
|
+
if equation is not None:
|
85
|
+
self.equation: str = equation
|
86
|
+
check = False
|
87
|
+
t = hasattr(self, "trueAnswers")
|
88
|
+
f = hasattr(self, "falseAnswers")
|
89
|
+
eq = hasattr(self, "equation")
|
90
|
+
if t and f and not eq:
|
91
|
+
self._typ: Literal["MC", "NFM"] = "MC"
|
92
|
+
return True
|
93
|
+
if eq and not t and not f:
|
94
|
+
self._typ: Literal["MC", "NFM"] = "NFM"
|
95
|
+
self.nfResults: list[float] = []
|
96
|
+
return True
|
97
|
+
return False
|
98
|
+
|
99
|
+
def __repr__(self) -> str:
|
100
|
+
answers: str = (
|
101
|
+
self.equation
|
102
|
+
if self.typ == "NFM"
|
103
|
+
else f"{self.trueAnswers}\n {self.falseAnswers}"
|
104
|
+
)
|
105
|
+
return f"Cloze Part {self.typ}\n Answers: '{answers}'"
|
106
|
+
|
21
107
|
|
22
108
|
class ClozeQuestion(ParametricQuestion):
|
23
109
|
"""Cloze Question Type."""
|
24
110
|
|
25
111
|
def __init__(self, *args, **kwargs) -> None:
|
26
112
|
super().__init__(*args, **kwargs)
|
27
|
-
self.
|
28
|
-
self.
|
29
|
-
|
30
|
-
|
31
|
-
|
113
|
+
self.questionParts: dict[int, ClozePart] = {}
|
114
|
+
self.questionTexts: list[ET.Element] = []
|
115
|
+
|
116
|
+
@property
|
117
|
+
def partsNum(self) -> int:
|
118
|
+
return len(self.questionParts)
|
119
|
+
|
120
|
+
@property
|
121
|
+
def points(self) -> float:
|
122
|
+
pts: float = 0
|
123
|
+
if self.isParsed:
|
124
|
+
for p in self.questionParts.values():
|
125
|
+
pts = pts + p.points
|
126
|
+
else:
|
127
|
+
pts = self.rawData.get(Tags.POINTS)
|
128
|
+
return pts
|
32
129
|
|
33
130
|
def _assembleAnswer(self, variant: int = 1) -> None:
|
34
|
-
for
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
131
|
+
for partNum, part in self.questionParts.items():
|
132
|
+
if part.typ == "MC":
|
133
|
+
ansStr = part.mcAnswerString
|
134
|
+
self.logger.info("MC answer part: %s ", ansStr)
|
135
|
+
elif part.typ == "NFM":
|
136
|
+
result = part.nfResults[variant - 1]
|
39
137
|
ansStr = ClozeQuestionParser.getNumericAnsStr(
|
40
138
|
result,
|
41
139
|
self.rawData.get(Tags.TOLERANCE),
|
42
140
|
wrongSignCount=self.rawData.get(Tags.WRONGSIGNPERCENT),
|
141
|
+
points=part.points,
|
43
142
|
)
|
143
|
+
self.logger.info("NF answer part: %s ", ansStr)
|
144
|
+
else:
|
145
|
+
msg = "Type of the answer part is invalid"
|
146
|
+
raise QNotParsedException(msg, self.id)
|
44
147
|
ul = TextElements.ULIST.create()
|
45
148
|
item = TextElements.LISTITEM.create()
|
46
149
|
item.text = ansStr
|
47
150
|
ul.append(item)
|
48
|
-
|
49
|
-
self.logger.debug("Appended
|
50
|
-
|
151
|
+
part.text.append(ul)
|
152
|
+
self.logger.debug("Appended part %s %s to main text", partNum, part)
|
153
|
+
part.text.append(ET.Element("hr"))
|
154
|
+
self.questionTexts.extend(part.text)
|
51
155
|
|
52
156
|
def _assembleText(self, variant=0) -> list[ET.Element]:
|
53
157
|
textParts = super()._assembleText(variant=variant)
|
54
158
|
self.logger.debug("Appending QuestionParts to main text")
|
55
|
-
|
56
|
-
for par in paragraphs:
|
57
|
-
textParts.append(par)
|
159
|
+
textParts.extend(self.questionTexts)
|
58
160
|
return textParts
|
59
161
|
|
60
162
|
|
@@ -70,67 +172,99 @@ class ClozeQuestionParser(NFMQuestionParser):
|
|
70
172
|
super().setup(question)
|
71
173
|
|
72
174
|
def _parseAnswers(self) -> None:
|
175
|
+
self._setupParts()
|
73
176
|
self._parseAnswerParts()
|
74
|
-
self._parseQuestionParts()
|
75
177
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
self.getPartNumber(key): self.rawInput[key]
|
178
|
+
def _setupParts(self) -> None:
|
179
|
+
parts: dict[int, ClozePart] = {
|
180
|
+
self.getPartNumber(key): ClozePart(self.question, self.rawInput[key])
|
80
181
|
for key in self.rawInput
|
81
|
-
if key.startswith(Tags.
|
182
|
+
if key.startswith(Tags.QUESTIONPART)
|
82
183
|
}
|
83
|
-
|
184
|
+
partsNum = len(parts)
|
185
|
+
equations: dict[int, str] = self._getPartValues(Tags.RESULT)
|
186
|
+
trueAnsws: dict[int, list[str]] = self._getPartValues(Tags.TRUE)
|
187
|
+
falseAnsws: dict[int, list[str]] = self._getPartValues(Tags.FALSE)
|
188
|
+
points: dict[int, float] = self._getPartValues(Tags.POINTS)
|
189
|
+
for num, part in parts.items():
|
190
|
+
eq = equations.get(num)
|
191
|
+
true = trueAnsws.get(num)
|
192
|
+
false = falseAnsws.get(num)
|
193
|
+
part.setAnswer(equation=eq, trueAns=true, falseAns=false)
|
194
|
+
if len(points) == 0:
|
195
|
+
pts = round(self.rawInput.get(Tags.POINTS) / partsNum, 3)
|
196
|
+
for part in parts.values():
|
197
|
+
part.points = pts
|
198
|
+
elif len(points) != partsNum:
|
199
|
+
logger.warning(
|
200
|
+
"Some Answer parts are missing the points, they will get the standard points"
|
201
|
+
)
|
202
|
+
for num, part in parts.items():
|
203
|
+
p = points.get(num)
|
204
|
+
part.points = p if p is not None else self.rawInput.get(Tags.POINTS)
|
205
|
+
|
206
|
+
self.question.questionParts = parts
|
207
|
+
|
208
|
+
@overload
|
209
|
+
def _getPartValues(self, Tag: Literal[Tags.RESULT]) -> dict[int, str]: ...
|
210
|
+
@overload
|
211
|
+
def _getPartValues(self, Tag: Literal[Tags.POINTS]) -> dict[int, float]: ...
|
212
|
+
@overload
|
213
|
+
def _getPartValues(
|
214
|
+
self, Tag: Literal[Tags.TRUE, Tags.FALSE]
|
215
|
+
) -> dict[int, list[str]]: ...
|
216
|
+
def _getPartValues(self, Tag):
|
217
|
+
tagValues: dict = {
|
84
218
|
self.getPartNumber(key): self.rawInput[key]
|
85
219
|
for key in self.rawInput
|
86
|
-
if key.startswith(
|
220
|
+
if key.startswith(Tag)
|
87
221
|
}
|
88
|
-
self.logger.
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
222
|
+
self.logger.warning("Found part data %s: %s", Tag, tagValues)
|
223
|
+
return tagValues
|
224
|
+
|
225
|
+
def _parseAnswerParts(self) -> None:
|
226
|
+
"""Parse the numeric or MC result items."""
|
227
|
+
try:
|
228
|
+
bps = str(self.rawInput[Tags.BPOINTS])
|
229
|
+
except KeyError:
|
230
|
+
bps = None
|
231
|
+
number = 1
|
232
|
+
else:
|
233
|
+
varNames: list[str] = self._getVarsList(bps)
|
234
|
+
self.question.variables, number = self._getVariablesDict(varNames)
|
235
|
+
for variant in range(number):
|
236
|
+
self.setupAstIntprt(self.question.variables, variant)
|
237
|
+
for partNum, part in self.question.questionParts.items():
|
238
|
+
if part.typ == "NFM":
|
239
|
+
result = self._calculateNFMPartResult(part, partNum, variant)
|
240
|
+
part.nfResults.append(result)
|
241
|
+
logger.debug("Appended NF part %s result: %s", partNum, result)
|
242
|
+
elif part.typ == "MC":
|
243
|
+
ansStr = self.getMCAnsStr(
|
244
|
+
part.trueAnswers, part.falseAnswers, points=part.points
|
245
|
+
)
|
246
|
+
part.mcAnswerString = ansStr
|
247
|
+
logger.debug("Appended MC part %s: %s", partNum, ansStr)
|
111
248
|
self._setVariants(number)
|
112
249
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
# def setMainText(self) -> None:
|
132
|
-
# super().setMainText()
|
133
|
-
# self.question.qtextParagraphs
|
250
|
+
def _calculateNFMPartResult(
|
251
|
+
self, part: ClozePart, partNum: int, variant: int
|
252
|
+
) -> float:
|
253
|
+
result = self.astEval(part.equation)
|
254
|
+
if isinstance(result, float):
|
255
|
+
try:
|
256
|
+
firstResult = self.rawInput[f"{Tags.FIRSTRESULT}:{partNum}"]
|
257
|
+
except KeyError:
|
258
|
+
firstResult = 0.0
|
259
|
+
if variant == 0 and not math.isclose(result, firstResult, rel_tol=0.002):
|
260
|
+
self.logger.warning(
|
261
|
+
"The calculated result %s differs from given firstResult: %s",
|
262
|
+
result,
|
263
|
+
firstResult,
|
264
|
+
)
|
265
|
+
return result
|
266
|
+
msg = f"The expression {part.equation} could not be evaluated."
|
267
|
+
raise QNotParsedException(msg, self.question.id)
|
134
268
|
|
135
269
|
def getPartNumber(self, indexKey: str) -> int:
|
136
270
|
"""Return the number of the question Part.
|
@@ -151,7 +285,7 @@ class ClozeQuestionParser(NFMQuestionParser):
|
|
151
285
|
def getNumericAnsStr(
|
152
286
|
result: float,
|
153
287
|
tolerance: float,
|
154
|
-
|
288
|
+
points: float = 1,
|
155
289
|
wrongSignCount: int = 50,
|
156
290
|
wrongSignFeedback: str = "your result has the wrong sign (+-)",
|
157
291
|
) -> str:
|
@@ -159,12 +293,6 @@ class ClozeQuestionParser(NFMQuestionParser):
|
|
159
293
|
|
160
294
|
Parameters.
|
161
295
|
----------
|
162
|
-
weight:
|
163
|
-
The weight of the answer relative to the other answer elements.
|
164
|
-
Of one answer has `weight=2` and two other answers `weight=1`,
|
165
|
-
this answer will be counted as 50% of the questions points.
|
166
|
-
The other two will counted as 25% of the questions points.
|
167
|
-
|
168
296
|
wrongSignCount:
|
169
297
|
If the wrong sign `+` or `-` is given, how much of the points should be given.
|
170
298
|
Interpreted as percent.
|
@@ -175,7 +303,7 @@ class ClozeQuestionParser(NFMQuestionParser):
|
|
175
303
|
absTol = f":{round(result * tolerance, 3)}"
|
176
304
|
answerParts: list[str | float] = [
|
177
305
|
"{",
|
178
|
-
|
306
|
+
points,
|
179
307
|
":NUMERICAL:=",
|
180
308
|
round(result, 3),
|
181
309
|
absTol,
|
@@ -194,14 +322,21 @@ class ClozeQuestionParser(NFMQuestionParser):
|
|
194
322
|
def getMCAnsStr(
|
195
323
|
true: list[str],
|
196
324
|
false: list[str],
|
197
|
-
|
325
|
+
points: float = 1,
|
198
326
|
) -> str:
|
199
327
|
"""Generate the answer string for the MC answers."""
|
328
|
+
truePercent: float = round(100 / len(true), 1)
|
329
|
+
falsePercent: float = round(100 / len(false), 1)
|
330
|
+
falseList: list[str] = [f"~%-{falsePercent}%{ans}" for ans in false]
|
331
|
+
trueList: list[str] = [f"~%{truePercent}%{ans}" for ans in true]
|
200
332
|
answerParts: list[str | float] = [
|
201
333
|
"{",
|
202
|
-
|
203
|
-
":
|
204
|
-
"}",
|
334
|
+
points,
|
335
|
+
":MULTIRESPONSE:",
|
205
336
|
]
|
337
|
+
answerParts.extend(trueList)
|
338
|
+
answerParts.extend(falseList)
|
339
|
+
answerParts.append("}")
|
340
|
+
|
206
341
|
answerPStrings = [str(part) for part in answerParts]
|
207
|
-
return "".join(
|
342
|
+
return "".join(answerPStrings)
|
@@ -45,14 +45,15 @@ class NFMQuestionParser(QuestionParser):
|
|
45
45
|
super().__init__()
|
46
46
|
self.genFeedbacks = [XMLTags.GENFEEDB]
|
47
47
|
self.question: NFMQuestion
|
48
|
-
module = self.settings.get(Tags.IMPORTMODULE)
|
49
|
-
if module and not type(self).astEval.symtable.get(module):
|
50
|
-
type(self).astEval(f"import {module}")
|
51
|
-
self.logger.warning("Imported '%s' to Asteval symtable", module)
|
52
48
|
|
53
49
|
def setup(self, question: NFMQuestion) -> None:
|
54
50
|
self.question: NFMQuestion = question
|
55
51
|
super().setup(question)
|
52
|
+
module = self.settings.get(Tags.IMPORTMODULE)
|
53
|
+
if module and type(self).astEval.symtable.get(module, None) is None:
|
54
|
+
type(self).astEval(f"import {module}")
|
55
|
+
imported = type(self).astEval.symtable.get(module)
|
56
|
+
self.logger.warning("Imported '%s' to Asteval symtable.", module)
|
56
57
|
|
57
58
|
def _parseAnswers(self) -> None:
|
58
59
|
equation = self.rawInput.get(Tags.EQUATION)
|
excel2moodle/ui/appUi.py
CHANGED
@@ -51,23 +51,27 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
51
51
|
self.ui.treeWidget.header().setSectionResizeMode(
|
52
52
|
QtWidgets.QHeaderView.ResizeToContents,
|
53
53
|
)
|
54
|
+
self.ui.pointCounter.setReadOnly(True)
|
55
|
+
self.ui.questionCounter.setReadOnly(True)
|
56
|
+
self.setStatus(
|
57
|
+
"Wählen Sie eine Excel Tabelle mit den Fragen aus",
|
58
|
+
)
|
59
|
+
self.threadPool = QThreadPool()
|
60
|
+
self._restoreSettings()
|
61
|
+
|
62
|
+
def _restoreSettings(self) -> None:
|
63
|
+
"""Restore the settings from the last session, if they exist."""
|
54
64
|
self.exportDialog.ui.checkBoxIncludeCategories.setChecked(
|
55
65
|
self.qSettings.value(Tags.INCLUDEINCATS, defaultValue=True, type=bool)
|
56
66
|
)
|
57
67
|
self.exportDialog.ui.spinBoxDefaultQVariant.setValue(
|
58
68
|
self.qSettings.value(Tags.QUESTIONVARIANT, defaultValue=1, type=int)
|
59
69
|
)
|
60
|
-
self.ui.pointCounter.setReadOnly(True)
|
61
|
-
self.ui.questionCounter.setReadOnly(True)
|
62
|
-
self.setStatus(
|
63
|
-
"Wählen Sie eine Excel Tabelle mit den Fragen aus",
|
64
|
-
)
|
65
70
|
try:
|
66
71
|
self.resize(self.qSettings.value("windowSize"))
|
67
72
|
self.move(self.qSettings.value("windowPosition"))
|
68
73
|
except Exception:
|
69
74
|
pass
|
70
|
-
self.threadPool = QThreadPool()
|
71
75
|
if self.qSettings.contains(Tags.SPREADSHEETPATH.full):
|
72
76
|
sheet = self.qSettings.value(Tags.SPREADSHEETPATH.full)
|
73
77
|
self.setSheetPath(sheet)
|
@@ -79,9 +83,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
79
83
|
)
|
80
84
|
loggerSignal.emitter.signal.connect(self.updateLog)
|
81
85
|
self.ui.actionEquationChecker.triggered.connect(self.openEqCheckerDlg)
|
82
|
-
self.exportDialog.ui.checkBoxIncludeCategories.checkStateChanged.connect(
|
83
|
-
self.setIncludeCategoriesSetting,
|
84
|
-
)
|
85
86
|
self.ui.actionParseAll.triggered.connect(self.parseSpreadsheetAll)
|
86
87
|
self.testDB.signals.categoryQuestionsReady.connect(self.treeRefreshCategory)
|
87
88
|
self.ui.actionSpreadsheet.triggered.connect(self.actionSpreadsheet)
|
@@ -91,13 +92,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
91
92
|
self.ui.treeWidget.itemClicked.connect(self.updateQuestionPreview)
|
92
93
|
self.ui.actionAbout.triggered.connect(self.openAboutDlg)
|
93
94
|
self.ui.actionDocumentation.triggered.connect(self.openDocumentation)
|
94
|
-
self.exportDialog.ui.spinBoxDefaultQVariant.valueChanged.connect(
|
95
|
-
self.setQVariantDefault
|
96
|
-
)
|
97
|
-
|
98
|
-
@QtCore.Slot()
|
99
|
-
def setQVariantDefault(self, value: int) -> None:
|
100
|
-
self.settings.set(Tags.QUESTIONVARIANT, value)
|
101
95
|
|
102
96
|
@QtCore.Slot()
|
103
97
|
def parseSpreadsheetAll(self) -> None:
|
@@ -128,12 +122,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
128
122
|
def updateLog(self, log) -> None:
|
129
123
|
self.ui.loggerWindow.append(log)
|
130
124
|
|
131
|
-
def setIncludeCategoriesSetting(self) -> None:
|
132
|
-
if self.exportDialog.ui.checkBoxIncludeCategories.isChecked():
|
133
|
-
self.settings.set(Tags.INCLUDEINCATS, True)
|
134
|
-
else:
|
135
|
-
self.settings.set(Tags.INCLUDEINCATS, False)
|
136
|
-
|
137
125
|
def closeEvent(self, event) -> None:
|
138
126
|
logger.info("Closing. Saving window stats.")
|
139
127
|
self.qSettings.setValue("windowSize", self.size())
|
@@ -179,6 +167,14 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
179
167
|
self.exportDialog.ui.pointCount.setValue(self.ui.pointCounter.value())
|
180
168
|
if self.exportDialog.exec():
|
181
169
|
self.exportFile = self.exportDialog.exportFile
|
170
|
+
self.settings.set(
|
171
|
+
Tags.INCLUDEINCATS,
|
172
|
+
self.exportDialog.ui.checkBoxIncludeCategories.isChecked(),
|
173
|
+
)
|
174
|
+
self.settings.set(
|
175
|
+
Tags.QUESTIONVARIANT,
|
176
|
+
self.exportDialog.ui.spinBoxDefaultQVariant.value(),
|
177
|
+
)
|
182
178
|
logger.info("New Export File is set %s", self.exportFile)
|
183
179
|
self.testDB.appendQuestions(selection, self.exportFile)
|
184
180
|
else:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: excel2moodle
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.2
|
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
|
@@ -81,6 +81,46 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
|
|
81
81
|
|
82
82
|
# Changelogs
|
83
83
|
|
84
|
+
## 0.5.2 (2025-06-30)
|
85
|
+
Extended Documentation and bugfix for import Module
|
86
|
+
|
87
|
+
### bugfix (2 changes)
|
88
|
+
|
89
|
+
- [Default question variant saved and reused.](https://gitlab.com/jbosse3/excel2moodle/-/commit/097705ba83727463a9b27cd76e99814a7ecf28df)
|
90
|
+
- [bugfix: Import module working again](https://gitlab.com/jbosse3/excel2moodle/-/commit/5f293970bcdac3858911cdcc102b72714af057bd)
|
91
|
+
|
92
|
+
### documentation (1 change)
|
93
|
+
|
94
|
+
- [documentation: Added how to build question database](https://gitlab.com/jbosse3/excel2moodle/-/commit/71ceb122aa37e8bf2735b659359ae37d81017599)
|
95
|
+
|
96
|
+
### feature (1 change)
|
97
|
+
|
98
|
+
- [Implemented MC question string method](https://gitlab.com/jbosse3/excel2moodle/-/commit/c4f2081d0000ee60322fe8eec8468fa3317ce7be)
|
99
|
+
|
100
|
+
### improvement (1 change)
|
101
|
+
|
102
|
+
- [Implemented ClozePart object](https://gitlab.com/jbosse3/excel2moodle/-/commit/878f90f45e37421384c4f8f602115e7596b4ceb9)
|
103
|
+
|
104
|
+
## 0.5.2 (2025-06-30)
|
105
|
+
Extended Documentation and bugfix for import Module
|
106
|
+
|
107
|
+
### bugfix (2 changes)
|
108
|
+
|
109
|
+
- [Default question variant saved and reused.](https://gitlab.com/jbosse3/excel2moodle/-/commit/097705ba83727463a9b27cd76e99814a7ecf28df)
|
110
|
+
- [bugfix: Import module working again](https://gitlab.com/jbosse3/excel2moodle/-/commit/5f293970bcdac3858911cdcc102b72714af057bd)
|
111
|
+
|
112
|
+
### documentation (1 change)
|
113
|
+
|
114
|
+
- [documentation: Added how to build question database](https://gitlab.com/jbosse3/excel2moodle/-/commit/71ceb122aa37e8bf2735b659359ae37d81017599)
|
115
|
+
|
116
|
+
### feature (1 change)
|
117
|
+
|
118
|
+
- [Implemented MC question string method](https://gitlab.com/jbosse3/excel2moodle/-/commit/c4f2081d0000ee60322fe8eec8468fa3317ce7be)
|
119
|
+
|
120
|
+
### improvement (1 change)
|
121
|
+
|
122
|
+
- [Implemented ClozePart object](https://gitlab.com/jbosse3/excel2moodle/-/commit/878f90f45e37421384c4f8f602115e7596b4ceb9)
|
123
|
+
|
84
124
|
## 0.5.1 (2025-06-24)
|
85
125
|
Minor docs improvement and question variant bugfix
|
86
126
|
|
@@ -3,36 +3,36 @@ excel2moodle/__main__.py,sha256=sG4ygwfVFskLQorBn-v98SvasNcPmwl_vLYpruT5Hk8,1175
|
|
3
3
|
excel2moodle/logger.py,sha256=fq8ZOkCI1wj38v8IyrZsUlpt16onlSH_phqbVvYUwBQ,3725
|
4
4
|
excel2moodle/core/__init__.py,sha256=87BwhtZse72Tk17Ib-V9X2k9wkhmtVnEj2ZmJ9JBAnI,63
|
5
5
|
excel2moodle/core/category.py,sha256=wLzpbweQbzaItdbp2NCPI_Zmk94fy1EDOwEEN8zPvkU,2123
|
6
|
-
excel2moodle/core/dataStructure.py,sha256=
|
6
|
+
excel2moodle/core/dataStructure.py,sha256=0XVu6NqKKNrjL0B9UVD9di-uqriGIdF-57lwx0ckeDI,16026
|
7
7
|
excel2moodle/core/etHelpers.py,sha256=G37qplp8tPJxqHNCBrf2Wo0jJZ0aDbxE9slQavqYqd8,2293
|
8
8
|
excel2moodle/core/exceptions.py,sha256=9xfsaIcm6Yej6QAZga0d3DK3jLQejdfgJARuAaG-uZY,739
|
9
9
|
excel2moodle/core/globals.py,sha256=Zm1wcrzQTRnhjrkwgBvo7VjKCFdPMjh-VLSSI5_QCO8,2837
|
10
10
|
excel2moodle/core/numericMultiQ.py,sha256=vr-gYogu2sf2a_Bhvhnu1ZSZFZXM32MfhJesjTkoOQM,2618
|
11
11
|
excel2moodle/core/parser.py,sha256=y0BXXt5j-4gRZO8otmEZ1Rmb0DW7hziesUoZ2kVpo9Y,8235
|
12
|
-
excel2moodle/core/question.py,sha256=
|
12
|
+
excel2moodle/core/question.py,sha256=_BSaMdDB269Q4Ag7izegMiExjYRTYOuhYe-qo92EAAg,11726
|
13
13
|
excel2moodle/core/settings.py,sha256=27D-P44rYk-DMrwI1dNpxHcznpFQf1W3XZrOc8e6rX4,5855
|
14
14
|
excel2moodle/core/stringHelpers.py,sha256=OzFZ6Eu3PeBLKb61K-aeVfUZmVuBerr9KfyOsuNRd7Y,2403
|
15
15
|
excel2moodle/core/validator.py,sha256=ssgkyUwrR-0AGPX1cUqvRwZsGja13J7HQ2W72ltqN-Y,4683
|
16
16
|
excel2moodle/extra/__init__.py,sha256=PM-id60HD21A3IcGC_fCYFihS8osBGZMIJCcN-ZRsIM,293
|
17
17
|
excel2moodle/extra/equationVerification.py,sha256=GLJl1r90d8AAiNy0H2hooZrg3D6aEwNfifYKAe3aGxM,3921
|
18
18
|
excel2moodle/question_types/__init__.py,sha256=81mss0g7SVtnlb-WkydE28G_dEAAf6oT1uB8lpK2-II,1041
|
19
|
-
excel2moodle/question_types/cloze.py,sha256
|
19
|
+
excel2moodle/question_types/cloze.py,sha256=-F2CozuNHRvTDQM69DgkKXTvw9wMLXfipkdhfTNWSSs,11969
|
20
20
|
excel2moodle/question_types/mc.py,sha256=2kn6dPjFVg97H8SlUBFbcPjzDk84vgDGCMOtSABseu0,5225
|
21
21
|
excel2moodle/question_types/nf.py,sha256=bMP4IXrhnXmAI0NmjEc7DtX4xGaUbxzLicE2LjeaUho,1150
|
22
|
-
excel2moodle/question_types/nfm.py,sha256=
|
22
|
+
excel2moodle/question_types/nfm.py,sha256=LTQ60qrhYUqKXoZW4AKz352rD4pgN0wAX_tm5FLFbV0,5022
|
23
23
|
excel2moodle/ui/UI_equationChecker.py,sha256=evQDlqCHeooJcAnYjhFCyjlPhfknr7ULGKQwMmqQeJ4,8947
|
24
24
|
excel2moodle/ui/UI_exportSettingsDialog.py,sha256=71xxXEqtewN0ReMfJ5t4gbrX_Bf0VEuxJ_DIV7ZtH94,6045
|
25
25
|
excel2moodle/ui/UI_mainWindow.py,sha256=asWUmKIYqufKUvRuCuA1JoMyv4qfRXyoR70F0331lww,19291
|
26
26
|
excel2moodle/ui/UI_variantDialog.py,sha256=snVaF3_YAc7NWjMRg7NzbjL_PzNbOpt4eiqElkE46io,5414
|
27
27
|
excel2moodle/ui/__init__.py,sha256=4EdGtpzwH3rgw4xW9E5x9kdPQYwKbo9rehHRZTNxCrQ,44
|
28
|
-
excel2moodle/ui/appUi.py,sha256=
|
28
|
+
excel2moodle/ui/appUi.py,sha256=caNdBb4_C2Ptl0He2iCQqoxw6Tm0wmJL9eOoKfjMah8,10681
|
29
29
|
excel2moodle/ui/dialogs.py,sha256=0h6aD4tguph1P07dorkn1A5B7_Z5SJZQ2_8xBYWK6MU,7689
|
30
30
|
excel2moodle/ui/equationChecker.py,sha256=ANpN7S0llkp6pGL1sKHII1Jc8YUvgDR458UnGVnZZOo,2702
|
31
31
|
excel2moodle/ui/treewidget.py,sha256=az64swVj1yQUsioeaZys32AauvQDdC4EKcqdbbWgL6s,2489
|
32
32
|
excel2moodle/ui/windowDoc.py,sha256=WvzHj6F4JvHP82WlTsyFeOXW024Xq3BUqtp--T4twuI,661
|
33
|
-
excel2moodle-0.5.
|
34
|
-
excel2moodle-0.5.
|
35
|
-
excel2moodle-0.5.
|
36
|
-
excel2moodle-0.5.
|
37
|
-
excel2moodle-0.5.
|
38
|
-
excel2moodle-0.5.
|
33
|
+
excel2moodle-0.5.2.dist-info/licenses/LICENSE,sha256=ywQqe6Sitymkf2lV2NRcx_aGsaC-KbSl_EfEsRXmNRw,35135
|
34
|
+
excel2moodle-0.5.2.dist-info/METADATA,sha256=pskuhRVnt-PEmvxVxvPUvN03gHmilnZqeFluoPOkPrk,6330
|
35
|
+
excel2moodle-0.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
36
|
+
excel2moodle-0.5.2.dist-info/entry_points.txt,sha256=myfMLDThuGgWHMJDPPfILiZqo_7D3fhmDdJGqWOAjPw,60
|
37
|
+
excel2moodle-0.5.2.dist-info/top_level.txt,sha256=5V1xRUQ9o7UmOCmNoWCZPAuy5nXp3Qbzyqch8fUGT_c,13
|
38
|
+
excel2moodle-0.5.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|