excel2moodle 0.5.0__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 +6 -6
- excel2moodle/question_types/cloze.py +221 -86
- excel2moodle/question_types/nfm.py +6 -5
- excel2moodle/ui/appUi.py +18 -22
- excel2moodle/ui/dialogs.py +9 -5
- excel2moodle-0.5.2.dist-info/METADATA +146 -0
- {excel2moodle-0.5.0.dist-info → excel2moodle-0.5.2.dist-info}/RECORD +12 -12
- excel2moodle-0.5.0.dist-info/METADATA +0 -63
- {excel2moodle-0.5.0.dist-info → excel2moodle-0.5.2.dist-info}/WHEEL +0 -0
- {excel2moodle-0.5.0.dist-info → excel2moodle-0.5.2.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.5.0.dist-info → excel2moodle-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.5.0.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,15 +161,15 @@ 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.
|
165
|
-
self.
|
166
|
-
textParts = self.
|
164
|
+
self.logger.info("Starting assembly variant: %s", variant)
|
165
|
+
self._assembleAnswer(variant=variant)
|
166
|
+
textParts = self._assembleText(variant=variant)
|
167
167
|
if hasattr(self, "picture") and self.picture.ready:
|
168
168
|
mainText.append(self.picture.element)
|
169
169
|
self.logger.debug("Appended Picture element to text")
|
170
170
|
mainText.append(etHelpers.getCdatTxtElement(textParts))
|
171
171
|
|
172
|
-
def
|
172
|
+
def _assembleText(self, variant=0) -> list[ET.Element]:
|
173
173
|
"""Assemble the Question Text.
|
174
174
|
|
175
175
|
Intended for the cloze question, where the answers parts are part of the text.
|
@@ -202,7 +202,7 @@ class Question:
|
|
202
202
|
return self.bulletList
|
203
203
|
return None
|
204
204
|
|
205
|
-
def
|
205
|
+
def _assembleAnswer(self, variant: int = 0) -> None:
|
206
206
|
pass
|
207
207
|
|
208
208
|
def _setID(self, id=0) -> None:
|
@@ -229,7 +229,7 @@ class ParametricQuestion(Question):
|
|
229
229
|
def replaceMatch(match: Match[str]) -> str | int | float:
|
230
230
|
key = match.group(1)
|
231
231
|
if key in self.variables:
|
232
|
-
value = self.variables[key][variant]
|
232
|
+
value = self.variables[key][variant - 1]
|
233
233
|
return f"{value}".replace(".", ",\\!")
|
234
234
|
return match.group(0) # keep original if no match
|
235
235
|
|
@@ -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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
129
|
+
|
130
|
+
def _assembleAnswer(self, variant: int = 1) -> None:
|
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
|
-
def
|
53
|
-
textParts = super().
|
156
|
+
def _assembleText(self, variant=0) -> list[ET.Element]:
|
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)
|
@@ -30,7 +30,7 @@ class NFMQuestion(ParametricQuestion):
|
|
30
30
|
super().__init__(*args, **kwargs)
|
31
31
|
self.answerVariants: list[ET.Element]
|
32
32
|
|
33
|
-
def
|
33
|
+
def _assembleAnswer(self, variant: int = 1) -> None:
|
34
34
|
prevAnsElement = self.element.find(XMLTags.ANSWER)
|
35
35
|
if prevAnsElement is not None:
|
36
36
|
self.element.remove(prevAnsElement)
|
@@ -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:
|
excel2moodle/ui/dialogs.py
CHANGED
@@ -164,16 +164,20 @@ class AboutDialog(QtWidgets.QMessageBox):
|
|
164
164
|
self.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Close)
|
165
165
|
|
166
166
|
self.aboutMessage: str = f"""
|
167
|
-
<h1> About {e2mMetadata["name"]} v
|
167
|
+
<h1> About {e2mMetadata["name"]} v{e2mMetadata["version"]}</h1><br>
|
168
168
|
<p style="text-align:center">
|
169
169
|
|
170
170
|
<b><a href="{e2mMetadata["homepage"]}">{e2mMetadata["name"]}</a> - {e2mMetadata["description"]}</b>
|
171
171
|
</p>
|
172
172
|
<p style="text-align:center">
|
173
|
-
|
174
|
-
<a href="{e2mMetadata["documentation"]}">documentation</a></b>
|
175
|
-
is also available.
|
173
|
+
If you need help you can find some <a href="https://gitlab.com/jbosse3/excel2moodle/-/example/"> examples.</a>
|
176
174
|
</br>
|
175
|
+
A Documentation can be viewed by clicking "F1",
|
176
|
+
or onto the documentation button.
|
177
|
+
</br>
|
178
|
+
</p>
|
179
|
+
<p style="text-align:center">
|
180
|
+
To see whats new in version {e2mMetadata["version"]} see the <a href="https://gitlab.com/jbosse3/excel2moodle#changelogs"> changelogs.</a>
|
177
181
|
</p>
|
178
182
|
<p style="text-align:center">
|
179
183
|
This project is maintained by {e2mMetadata["author"]}.
|
@@ -181,7 +185,7 @@ class AboutDialog(QtWidgets.QMessageBox):
|
|
181
185
|
Development takes place at <a href="{e2mMetadata["homepage"]}"> GitLab: {e2mMetadata["homepage"]}</a>
|
182
186
|
contributions are very welcome
|
183
187
|
</br>
|
184
|
-
If you encounter any issues please report them under the repositories issues page
|
188
|
+
If you encounter any issues please report them under the <a href="https://gitlab.com/jbosse3/excel2moodle/-/issues/"> repositories issues page </a>.
|
185
189
|
</br>
|
186
190
|
</p>
|
187
191
|
<p style="text-align:center">
|
@@ -0,0 +1,146 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: excel2moodle
|
3
|
+
Version: 0.5.2
|
4
|
+
Summary: A package for converting questions from a spreadsheet, to valid moodle-xml
|
5
|
+
Author: Jakob Bosse
|
6
|
+
License-Expression: GPL-3.0-or-later
|
7
|
+
Project-URL: Repository, https://gitlab.com/jbosse3/excel2moodle.git
|
8
|
+
Project-URL: Documentation, https://jbosse3.gitlab.io/excel2moodle
|
9
|
+
Keywords: moodle,XML,teaching,question,converter,open educational Ressource
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Requires-Python: >=3.10
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
License-File: LICENSE
|
15
|
+
Requires-Dist: pyside6>=6.8.0
|
16
|
+
Requires-Dist: pandas>=2.1.3
|
17
|
+
Requires-Dist: lxml>=5.4.0
|
18
|
+
Requires-Dist: asteval>=1.0.6
|
19
|
+
Requires-Dist: python-calamine>=0.3.2
|
20
|
+
Dynamic: license-file
|
21
|
+
|
22
|
+
# excel 2 Moodle
|
23
|
+
{width=35%}
|
24
|
+
|
25
|
+
This Python program helps to create Moodle questions in less time.
|
26
|
+
The idea is to write the questions data into a spreadsheet file, from which the program generates the moodle compliant xml Files.
|
27
|
+
All questions or a selection of questions can be exported into one xml file to be imported into moodle.
|
28
|
+
|
29
|
+
## Concept
|
30
|
+
The concept is, to store the different questions into categories of similar types and difficulties of questions, for each of which, a separated sheet in the Spreadsheet document should be created.
|
31
|
+
|
32
|
+
A `settings` sheet contains global settings to be used for all questions and categories.
|
33
|
+
Another sheet stores metadata for the different categories of questions.
|
34
|
+
And each category lives inside a separate sheet inside the spreadsheet document.
|
35
|
+
|
36
|
+
## Getting Started
|
37
|
+
|
38
|
+
### Installation
|
39
|
+
To get started with excel2moodle first have a look at the [installation](https://jbosse3.gitlab.io/excel2moodle/howto.html#excel2moodle-unter-windows-installieren)
|
40
|
+
If you already have python and uv installed, it is as easy as running `uv tool install excel2moodle`.
|
41
|
+
|
42
|
+
### [ Documentation ](https://jbosse3.gitlab.io/excel2moodle/index.html)
|
43
|
+
Once excel2moodle is installed you can checkout the [example question sheet](https://gitlab.com/jbosse3/excel2moodle/-/tree/master/example?ref_type=heads)
|
44
|
+
in the repository.
|
45
|
+
|
46
|
+
Some steps are already documented as [ tutorials ](https://jbosse3.gitlab.io/excel2moodle/howto.html)
|
47
|
+
you can follow along.
|
48
|
+
|
49
|
+
And please have a look into the [**user Reference**](https://jbosse3.gitlab.io/excel2moodle/userReference.html)
|
50
|
+
of the documentation.
|
51
|
+
That part explains each part of defining a question.
|
52
|
+
|
53
|
+
|
54
|
+
## Functionality
|
55
|
+
* Equation Verification:
|
56
|
+
+ this tool helps you to validate the correct equation for the parametrized Questions.
|
57
|
+
* Question Preview:
|
58
|
+
+ This helps you when selecting the correct questions for the export.
|
59
|
+
* Export Options:
|
60
|
+
+ you can export the questions preserving the categories in moodle
|
61
|
+
|
62
|
+
### Question Types
|
63
|
+
* Generate multiple Choice Questions:
|
64
|
+
+ The answers can be pictures or normal text
|
65
|
+
* Generate Numeric Questions
|
66
|
+
* Generate parametrized numeric Questions
|
67
|
+
* Generate parametrized cloze Questions
|
68
|
+
|
69
|
+
|
70
|
+
{width=80%}
|
71
|
+
|
72
|
+
## Licensing and authorship
|
73
|
+
excel2moodle is lincensed under the latest [GNU GPL license](https://gitlab.com/jbosse3/excel2moodle/-/blob/master/LICENSE)
|
74
|
+
Initial development was made by Richard Lorenz, and later taken over by Jakob Bosse
|
75
|
+
|
76
|
+
## Supporting
|
77
|
+
A special thanks goes to the [Civil Engineering Departement of the Fachhochschule Potsdam](https://www.fh-potsdam.de/en/study-further-education/departments/civil-engineering-department)
|
78
|
+
where i was employed as a student associate to work on this project.
|
79
|
+
|
80
|
+
If You want to support my work as well, you can by me a [coffee](https://ko-fi.com/jbosse3)
|
81
|
+
|
82
|
+
# Changelogs
|
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
|
+
|
124
|
+
## 0.5.1 (2025-06-24)
|
125
|
+
Minor docs improvement and question variant bugfix
|
126
|
+
|
127
|
+
### bugfix (1 change)
|
128
|
+
|
129
|
+
- [Bullet points variant didn't get updated](https://gitlab.com/jbosse3/excel2moodle/-/commit/7b4ad9e9c8a4216167ae019859ebaa8def81d57f)
|
130
|
+
|
131
|
+
## 0.5.0 (2025-06-20)
|
132
|
+
settings handling improved
|
133
|
+
|
134
|
+
### feature (2 changes)
|
135
|
+
|
136
|
+
- [Pixmaps and vector graphics scaled to fit in preview](https://gitlab.com/jbosse3/excel2moodle/-/commit/00a6ef13fb2a0046d7641e24af6cf6f08642390e)
|
137
|
+
- [feature: category Settings implemented](https://gitlab.com/jbosse3/excel2moodle/-/commit/d673cc3f5ba06051aa37bc17a3ef0161121cb730)
|
138
|
+
|
139
|
+
### improvement (1 change)
|
140
|
+
|
141
|
+
- [Tolerance is harmonized by questionData.get()](https://gitlab.com/jbosse3/excel2moodle/-/commit/8d1724f4877e1584cc531b6b3f278bdea68b5831)
|
142
|
+
|
143
|
+
### Settings Errors are logged (1 change)
|
144
|
+
|
145
|
+
- [Log Errors in settings Sheet](https://gitlab.com/jbosse3/excel2moodle/-/commit/07e58f957c69ea818db1c5679cf89e287817ced3)
|
146
|
+
|
@@ -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=
|
29
|
-
excel2moodle/ui/dialogs.py,sha256=
|
28
|
+
excel2moodle/ui/appUi.py,sha256=caNdBb4_C2Ptl0He2iCQqoxw6Tm0wmJL9eOoKfjMah8,10681
|
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,,
|
@@ -1,63 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: excel2moodle
|
3
|
-
Version: 0.5.0
|
4
|
-
Summary: A package for converting questions from a spreadsheet, to valid moodle-xml
|
5
|
-
Author: Jakob Bosse
|
6
|
-
License-Expression: GPL-3.0-or-later
|
7
|
-
Project-URL: Repository, https://gitlab.com/jbosse3/excel2moodle.git
|
8
|
-
Project-URL: Documentation, https://jbosse3.gitlab.io/excel2moodle
|
9
|
-
Keywords: moodle,XML,teaching,question,converter,open educational Ressource
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: Operating System :: OS Independent
|
12
|
-
Requires-Python: >=3.10
|
13
|
-
Description-Content-Type: text/markdown
|
14
|
-
License-File: LICENSE
|
15
|
-
Requires-Dist: pyside6>=6.8.0
|
16
|
-
Requires-Dist: pandas>=2.1.3
|
17
|
-
Requires-Dist: lxml>=5.4.0
|
18
|
-
Requires-Dist: asteval>=1.0.6
|
19
|
-
Requires-Dist: python-calamine>=0.3.2
|
20
|
-
Dynamic: license-file
|
21
|
-
|
22
|
-
# excel 2 Moodle
|
23
|
-
[Deutsche README](https://gitlab.com/jbosse3/excel2moodle/-/blob/master/README.de.md)
|
24
|
-
|
25
|
-
{width=50%}
|
26
|
-
|
27
|
-
This Python program helps to create Moodle questions in less time.
|
28
|
-
The aim is to put alle the information for the questions into a spreadsheet file, and then parse it, to generate Moodle compliant XML-Files.
|
29
|
-
|
30
|
-
Furthermore this program lets you create a single XML-File with a selection of questions, that then can be imported to a Moodle-Test.
|
31
|
-
|
32
|
-
## Concept
|
33
|
-
The concept is, to store the different questions into categories of similar types and difficulties of questions, for each of which, a separated sheet in the Spreadsheet document should be created.
|
34
|
-
|
35
|
-
There Should be a sheet called "Kategorien", where an overview over the different categories is stored.
|
36
|
-
This sheet stores The names and descriptions, for all categories. The name have to be the same as the actual sheet names with the questions.
|
37
|
-
Furthermore the points used for grading, are set in the "Kategorien" sheet
|
38
|
-
|
39
|
-
|
40
|
-
## Development State
|
41
|
-
This program is still quite rough, with very litte robustness against faulty user input inside the Spreadsheet.
|
42
|
-
|
43
|
-
## Functionality
|
44
|
-
* Parse multiple Choice Questions, each into one XML file
|
45
|
-
* Parse Numeric Questions, each into one XML file
|
46
|
-
* create single XML File from a selection of questions
|
47
|
-
|
48
|
-
## Development Goals
|
49
|
-
* [X] creating an example spreadsheet
|
50
|
-
* [X] Export function, to create numerical Question version from a matrix of variables and corresponding correct Answers:
|
51
|
-
* similar to the calculated question Type, but with the benefit, of serving all students the same exact question
|
52
|
-
* [.] making it more robust:
|
53
|
-
* [X] Adding Error Messages when exporting
|
54
|
-
* [X] Creating logging
|
55
|
-
* [ ] Logging Errors to File
|
56
|
-
* [ ] making it Image File-Type agnostic
|
57
|
-
* [ ] Creating a Settings Menu
|
58
|
-
* [ ] Making keys in spreadsheet selectable in the Settings
|
59
|
-
* [ ] Setting image folder
|
60
|
-
|
61
|
-
## Licensing and authorship
|
62
|
-
excel2moodle is lincensed under the latest [GNU GPL license](https://gitlab.com/jbosse3/excel2moodle/-/blob/master/LICENSE)
|
63
|
-
Initial development was made by Richard Lorenz, and later taken over by Jakob Bosse
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|