excel2moodle 0.5.2__py3-none-any.whl → 0.6.1__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/__main__.py +3 -2
- excel2moodle/core/bullets.py +98 -0
- excel2moodle/core/dataStructure.py +3 -4
- excel2moodle/core/globals.py +3 -8
- excel2moodle/core/parser.py +37 -65
- excel2moodle/core/question.py +144 -76
- excel2moodle/extra/variableGenerator.py +250 -0
- excel2moodle/question_types/cloze.py +156 -125
- excel2moodle/question_types/nfm.py +38 -100
- excel2moodle/ui/UI_mainWindow.py +63 -36
- excel2moodle/ui/UI_variableGenerator.py +197 -0
- excel2moodle/ui/appUi.py +90 -23
- excel2moodle/ui/dialogs.py +44 -77
- excel2moodle/ui/equationChecker.py +2 -2
- excel2moodle/ui/treewidget.py +9 -24
- {excel2moodle-0.5.2.dist-info → excel2moodle-0.6.1.dist-info}/METADATA +34 -2
- {excel2moodle-0.5.2.dist-info → excel2moodle-0.6.1.dist-info}/RECORD +21 -20
- excel2moodle/core/numericMultiQ.py +0 -80
- excel2moodle/ui/windowDoc.py +0 -27
- {excel2moodle-0.5.2.dist-info → excel2moodle-0.6.1.dist-info}/WHEEL +0 -0
- {excel2moodle-0.5.2.dist-info → excel2moodle-0.6.1.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.5.2.dist-info → excel2moodle-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.5.2.dist-info → excel2moodle-0.6.1.dist-info}/top_level.txt +0 -0
excel2moodle/ui/appUi.py
CHANGED
@@ -7,13 +7,23 @@ It needs to be seperated from ``windowMain.py`` because that file will be change
|
|
7
7
|
import logging
|
8
8
|
from pathlib import Path
|
9
9
|
|
10
|
-
from PySide6 import
|
11
|
-
from PySide6.
|
10
|
+
from PySide6.QtCore import QRunnable, QSettings, Qt, QThreadPool, QUrl, Slot
|
11
|
+
from PySide6.QtGui import QDesktopServices
|
12
|
+
from PySide6.QtWidgets import (
|
13
|
+
QAbstractItemView,
|
14
|
+
QApplication,
|
15
|
+
QFileDialog,
|
16
|
+
QHeaderView,
|
17
|
+
QMainWindow,
|
18
|
+
QMessageBox,
|
19
|
+
)
|
12
20
|
|
13
|
-
from excel2moodle import mainLogger
|
21
|
+
from excel2moodle import e2mMetadata, mainLogger
|
14
22
|
from excel2moodle.core.category import Category
|
15
23
|
from excel2moodle.core.dataStructure import QuestionDB
|
24
|
+
from excel2moodle.core.question import ParametricQuestion
|
16
25
|
from excel2moodle.core.settings import Settings, Tags
|
26
|
+
from excel2moodle.extra.variableGenerator import VariableGeneratorDialog
|
17
27
|
from excel2moodle.logger import LogWindowHandler
|
18
28
|
from excel2moodle.question_types.mc import MCQuestion
|
19
29
|
from excel2moodle.question_types.nf import NFQuestion
|
@@ -21,7 +31,6 @@ from excel2moodle.ui import dialogs
|
|
21
31
|
from excel2moodle.ui.equationChecker import EqCheckerWindow
|
22
32
|
from excel2moodle.ui.treewidget import CategoryItem, QuestionItem
|
23
33
|
from excel2moodle.ui.UI_mainWindow import Ui_MoodleTestGenerator
|
24
|
-
from excel2moodle.ui.windowDoc import DocumentationWindow
|
25
34
|
|
26
35
|
logger = logging.getLogger(__name__)
|
27
36
|
|
@@ -29,7 +38,7 @@ loggerSignal = LogWindowHandler()
|
|
29
38
|
mainLogger.addHandler(loggerSignal)
|
30
39
|
|
31
40
|
|
32
|
-
class MainWindow(
|
41
|
+
class MainWindow(QMainWindow):
|
33
42
|
def __init__(self, settings: Settings, testDB: QuestionDB) -> None:
|
34
43
|
super().__init__()
|
35
44
|
self.settings = settings
|
@@ -47,9 +56,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
47
56
|
self.eqChecker = EqCheckerWindow()
|
48
57
|
self.connectEvents()
|
49
58
|
logger.info("Settings are stored under: %s", self.qSettings.fileName())
|
50
|
-
self.ui.treeWidget.setSelectionMode(
|
59
|
+
self.ui.treeWidget.setSelectionMode(QAbstractItemView.MultiSelection)
|
51
60
|
self.ui.treeWidget.header().setSectionResizeMode(
|
52
|
-
|
61
|
+
QHeaderView.ResizeToContents,
|
53
62
|
)
|
54
63
|
self.ui.pointCounter.setReadOnly(True)
|
55
64
|
self.ui.questionCounter.setReadOnly(True)
|
@@ -92,8 +101,13 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
92
101
|
self.ui.treeWidget.itemClicked.connect(self.updateQuestionPreview)
|
93
102
|
self.ui.actionAbout.triggered.connect(self.openAboutDlg)
|
94
103
|
self.ui.actionDocumentation.triggered.connect(self.openDocumentation)
|
104
|
+
self.ui.actionGenerateVariables.triggered.connect(self.openVariableGeneratorDlg)
|
105
|
+
self.ui.actionCopyVariables.triggered.connect(self.copyVariablesToClipboard)
|
106
|
+
self.ui.actionOpenSpreadsheetExternal.triggered.connect(
|
107
|
+
self.openSpreadsheetExternally
|
108
|
+
)
|
95
109
|
|
96
|
-
@
|
110
|
+
@Slot()
|
97
111
|
def parseSpreadsheetAll(self) -> None:
|
98
112
|
"""Event triggered by the *Tools/Parse all Questions* Event.
|
99
113
|
|
@@ -134,7 +148,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
134
148
|
self.settings.get(Tags.QUESTIONVARIANT),
|
135
149
|
)
|
136
150
|
|
137
|
-
@
|
151
|
+
@Slot()
|
138
152
|
def onSelectionChanged(self, **args) -> None:
|
139
153
|
"""Whenever the selection changes the total of selected points needs to be recalculated."""
|
140
154
|
count: int = 0
|
@@ -148,7 +162,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
148
162
|
if self.eqChecker.isVisible():
|
149
163
|
self.openEqCheckerDlg()
|
150
164
|
|
151
|
-
@
|
165
|
+
@Slot()
|
152
166
|
def toggleQuestionSelectionState(self, state) -> None:
|
153
167
|
setter = state == Qt.Checked
|
154
168
|
root = self.ui.treeWidget.invisibleRootItem()
|
@@ -158,7 +172,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
158
172
|
for q in range(qs):
|
159
173
|
root.child(i).child(q).setSelected(setter)
|
160
174
|
|
161
|
-
@
|
175
|
+
@Slot()
|
162
176
|
def onButGenTest(self) -> None:
|
163
177
|
"""Open a file Dialog so the export file may be choosen."""
|
164
178
|
selection: list[QuestionItem] = self.ui.treeWidget.selectedItems()
|
@@ -180,9 +194,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
180
194
|
else:
|
181
195
|
logger.info("Aborting Export")
|
182
196
|
|
183
|
-
@
|
197
|
+
@Slot()
|
184
198
|
def actionSpreadsheet(self) -> None:
|
185
|
-
file =
|
199
|
+
file = QFileDialog.getOpenFileName(
|
186
200
|
self,
|
187
201
|
self.tr("Open Spreadsheet"),
|
188
202
|
dir=str(self.mainPath),
|
@@ -192,7 +206,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
192
206
|
path = Path(file[0]).resolve(strict=True)
|
193
207
|
self.setSheetPath(path)
|
194
208
|
|
195
|
-
@
|
209
|
+
@Slot(Category)
|
196
210
|
def treeRefreshCategory(self, cat: Category) -> None:
|
197
211
|
"""Append Category with its Questions to the treewidget."""
|
198
212
|
catItem = CategoryItem(self.ui.treeWidget, cat)
|
@@ -201,7 +215,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
201
215
|
QuestionItem(catItem, q)
|
202
216
|
self.ui.treeWidget.sortItems(0, Qt.SortOrder.AscendingOrder)
|
203
217
|
|
204
|
-
@
|
218
|
+
@Slot()
|
205
219
|
def updateQuestionPreview(self) -> None:
|
206
220
|
item = self.ui.treeWidget.currentItem()
|
207
221
|
if isinstance(item, QuestionItem):
|
@@ -213,7 +227,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
213
227
|
self.ui.statusbar.clearMessage()
|
214
228
|
self.ui.statusbar.showMessage(self.tr(status))
|
215
229
|
|
216
|
-
@
|
230
|
+
@Slot()
|
217
231
|
def openEqCheckerDlg(self) -> None:
|
218
232
|
item = self.ui.treeWidget.currentItem()
|
219
233
|
if isinstance(item, QuestionItem):
|
@@ -227,18 +241,71 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
227
241
|
else:
|
228
242
|
logger.debug("No Question Item selected: %s", type(item))
|
229
243
|
|
230
|
-
@
|
244
|
+
@Slot()
|
231
245
|
def openAboutDlg(self) -> None:
|
232
246
|
about = dialogs.AboutDialog(self)
|
233
247
|
about.exec()
|
234
248
|
|
235
|
-
@
|
249
|
+
@Slot()
|
236
250
|
def openDocumentation(self) -> None:
|
237
|
-
|
238
|
-
|
251
|
+
url = QUrl(e2mMetadata["documentation"])
|
252
|
+
QDesktopServices.openUrl(url)
|
253
|
+
|
254
|
+
@Slot()
|
255
|
+
def openVariableGeneratorDlg(self) -> None:
|
256
|
+
item = self.ui.treeWidget.currentItem()
|
257
|
+
if isinstance(item, QuestionItem):
|
258
|
+
question = item.getQuestion()
|
259
|
+
if isinstance(question, ParametricQuestion):
|
260
|
+
dialog = VariableGeneratorDialog(self, parametrics=question.parametrics)
|
261
|
+
if dialog.exec():
|
262
|
+
self.questionPreview.setupQuestion(question)
|
263
|
+
logger.info("Updated QuestionItem display for %s", question.id)
|
264
|
+
self.copyVariablesToClipboard(
|
265
|
+
variables=question.parametrics.variables
|
266
|
+
)
|
267
|
+
else:
|
268
|
+
logger.warning("No variable sets were generated.")
|
269
|
+
else:
|
270
|
+
logger.info("Selected item is not a ParametricQuestion.")
|
239
271
|
else:
|
240
|
-
|
241
|
-
|
272
|
+
logger.info("No Question Item selected.")
|
273
|
+
|
274
|
+
@Slot()
|
275
|
+
def copyVariablesToClipboard(
|
276
|
+
self, variables: dict[str, list[float | int]] | None = None
|
277
|
+
) -> None:
|
278
|
+
if variables is None:
|
279
|
+
variables = {}
|
280
|
+
if not variables:
|
281
|
+
item = self.ui.treeWidget.currentItem()
|
282
|
+
if isinstance(item, QuestionItem):
|
283
|
+
question = item.getQuestion()
|
284
|
+
if isinstance(question, ParametricQuestion):
|
285
|
+
variables = question.parametrics.variables
|
286
|
+
varsList = [
|
287
|
+
f"{name}\t{'; '.join(map(str, vals))}"
|
288
|
+
for name, vals in variables.items()
|
289
|
+
]
|
290
|
+
clipb = QApplication.clipboard()
|
291
|
+
variablesStr = "\n".join(varsList)
|
292
|
+
clipb.setText(variablesStr)
|
293
|
+
logger.info("Copied all variables to the clipboard")
|
294
|
+
QMessageBox.information(
|
295
|
+
self,
|
296
|
+
"Variables Copied.",
|
297
|
+
"""All variables from the parametric Question are saved to the system clipboard.\n
|
298
|
+
You can paste them into the spreadsheet.
|
299
|
+
Make sure to import them with 'Tab' as the seperator.""",
|
300
|
+
)
|
301
|
+
|
302
|
+
@Slot()
|
303
|
+
def openSpreadsheetExternally(self) -> None:
|
304
|
+
if self.excelPath is None:
|
305
|
+
return
|
306
|
+
spreadsheetPath = QUrl(f"file://{self.excelPath.absolute()}")
|
307
|
+
logger.info("Opening: %s", spreadsheetPath)
|
308
|
+
QDesktopServices.openUrl(spreadsheetPath)
|
242
309
|
|
243
310
|
|
244
311
|
class ParseAllThread(QRunnable):
|
@@ -252,7 +319,7 @@ class ParseAllThread(QRunnable):
|
|
252
319
|
self.testDB = questionDB
|
253
320
|
self.mainApp = mainApp
|
254
321
|
|
255
|
-
@
|
322
|
+
@Slot()
|
256
323
|
def run(self) -> None:
|
257
324
|
self.testDB.readCategoriesMetadata()
|
258
325
|
self.testDB.asyncInitAllCategories(self.mainApp.excelPath)
|
excel2moodle/ui/dialogs.py
CHANGED
@@ -5,12 +5,13 @@ from pathlib import Path
|
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
7
|
import lxml.etree as ET
|
8
|
-
from PySide6 import
|
9
|
-
from PySide6.QtSvgWidgets import QGraphicsSvgItem
|
8
|
+
from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox, QWidget
|
10
9
|
|
11
10
|
from excel2moodle import e2mMetadata
|
12
11
|
from excel2moodle.core.globals import XMLTags
|
13
|
-
from excel2moodle.core.question import Question
|
12
|
+
from excel2moodle.core.question import ParametricQuestion, Question
|
13
|
+
from excel2moodle.core.settings import Tags
|
14
|
+
from excel2moodle.extra import variableGenerator
|
14
15
|
from excel2moodle.ui.UI_exportSettingsDialog import Ui_ExportDialog
|
15
16
|
from excel2moodle.ui.UI_variantDialog import Ui_Dialog
|
16
17
|
|
@@ -20,11 +21,11 @@ if TYPE_CHECKING:
|
|
20
21
|
logger = logging.getLogger(__name__)
|
21
22
|
|
22
23
|
|
23
|
-
class QuestionVariantDialog(
|
24
|
-
def __init__(self, parent, question:
|
24
|
+
class QuestionVariantDialog(QDialog):
|
25
|
+
def __init__(self, parent, question: ParametricQuestion) -> None:
|
25
26
|
super().__init__(parent)
|
26
27
|
self.setWindowTitle("Question Variant Dialog")
|
27
|
-
self.maxVal = question.variants
|
28
|
+
self.maxVal = question.parametrics.variants
|
28
29
|
self.ui = Ui_Dialog()
|
29
30
|
self.ui.setupUi(self)
|
30
31
|
self.ui.spinBox.setRange(1, self.maxVal)
|
@@ -41,7 +42,7 @@ class QuestionVariantDialog(QtWidgets.QDialog):
|
|
41
42
|
return self.ui.checkBox.isChecked()
|
42
43
|
|
43
44
|
|
44
|
-
class ExportDialog(
|
45
|
+
class ExportDialog(QDialog):
|
45
46
|
def __init__(self, parent) -> None:
|
46
47
|
super().__init__(parent)
|
47
48
|
self.setWindowTitle("Export question Selection")
|
@@ -62,7 +63,7 @@ class ExportDialog(QtWidgets.QDialog):
|
|
62
63
|
)
|
63
64
|
|
64
65
|
def getExportFile(self) -> None:
|
65
|
-
path =
|
66
|
+
path = QFileDialog.getSaveFileName(
|
66
67
|
self,
|
67
68
|
"Select Output File",
|
68
69
|
dir=str(self.exportFile),
|
@@ -83,85 +84,51 @@ class QuestionPreview:
|
|
83
84
|
self.ui = parent.ui
|
84
85
|
self.parent = parent
|
85
86
|
|
87
|
+
def _replaceImgPlaceholder(self, elementStr: str) -> str:
|
88
|
+
"""Replaces '@@PLUGINFILE@@' with the questions Img Folder path."""
|
89
|
+
return elementStr.replace(
|
90
|
+
"@@PLUGINFILE@@",
|
91
|
+
f"{self.pictureFolder}/{self.question.category.NAME}",
|
92
|
+
)
|
93
|
+
|
86
94
|
def setupQuestion(self, question: Question) -> None:
|
95
|
+
self.pictureFolder = self.parent.settings.get(Tags.PICTUREFOLDER)
|
96
|
+
self.ui.previewTextEdit.clear()
|
87
97
|
self.question: Question = question
|
88
98
|
self.ui.qNameLine.setText(f"{self.question.qtype} - {self.question.name}")
|
89
|
-
self.
|
90
|
-
|
91
|
-
|
99
|
+
self.ui.previewTextEdit.append(
|
100
|
+
self._replaceImgPlaceholder(
|
101
|
+
ET.tostring(self.question.htmlRoot, encoding="unicode")
|
102
|
+
)
|
103
|
+
)
|
104
|
+
self.parent.ui.tableVariables.hide()
|
92
105
|
self.setAnswers()
|
93
|
-
if hasattr(self, "picItem") and self.picItem.scene() == self.picScene:
|
94
|
-
logger.debug("removing Previous picture")
|
95
|
-
self.picScene.removeItem(self.picItem)
|
96
|
-
del self.picItem
|
97
|
-
self.setPicture()
|
98
|
-
|
99
|
-
def setPicture(self) -> None:
|
100
|
-
if hasattr(self.question, "picture") and self.question.picture.ready:
|
101
|
-
path = self.question.picture.path
|
102
|
-
if path.suffix == ".svg":
|
103
|
-
self.picItem = QGraphicsSvgItem(str(path))
|
104
|
-
else:
|
105
|
-
pic = QtGui.QPixmap(str(path))
|
106
|
-
self.picItem = QtWidgets.QGraphicsPixmapItem(pic)
|
107
|
-
if pic.isNull():
|
108
|
-
logger.warning("Picture null")
|
109
|
-
scale = self._getImgFittingScale()
|
110
|
-
self.picItem.setScale(scale)
|
111
|
-
self.picScene.addItem(self.picItem)
|
112
|
-
|
113
|
-
def _getImgFittingScale(self) -> float:
|
114
|
-
view_size = self.ui.graphicsView.viewport().size()
|
115
|
-
view_width = view_size.width()
|
116
|
-
view_height = view_size.height()
|
117
|
-
if isinstance(self.picItem, QtWidgets.QGraphicsPixmapItem):
|
118
|
-
original_size = self.picItem.pixmap().size()
|
119
|
-
elif isinstance(self.picItem, QGraphicsSvgItem):
|
120
|
-
original_size = self.picItem.renderer().defaultSize()
|
121
|
-
else:
|
122
|
-
return 1 # Unknown item type
|
123
|
-
scale_x = view_width / original_size.width()
|
124
|
-
scale_y = view_height / original_size.height()
|
125
|
-
return min(scale_x, scale_y)
|
126
|
-
|
127
|
-
def setText(self) -> None:
|
128
|
-
t = []
|
129
|
-
for text in self.question.qtextParagraphs:
|
130
|
-
t.append(ET.tostring(text, encoding="unicode"))
|
131
|
-
if self.question.bulletList is not None:
|
132
|
-
t.append(ET.tostring(self.question.bulletList, encoding="unicode"))
|
133
|
-
self.ui.questionText.setText("\n".join(t))
|
134
106
|
|
135
107
|
def setAnswers(self) -> None:
|
136
|
-
if self.question
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
list.append(textEle)
|
142
|
-
self.ui.answersLabel.setText(ET.tostring(list, encoding="unicode"))
|
108
|
+
if isinstance(self.question, ParametricQuestion):
|
109
|
+
variableGenerator.populateDataSetTable(
|
110
|
+
self.parent.ui.tableVariables, parametrics=self.question.parametrics
|
111
|
+
)
|
112
|
+
self.parent.ui.tableVariables.show()
|
143
113
|
elif self.question.qtype == "NF":
|
144
|
-
ans = self.question.
|
145
|
-
self.ui.
|
114
|
+
ans = self.question._element.find(XMLTags.ANSWER)
|
115
|
+
self.ui.previewTextEdit.append(f" Result: {ans.find('text').text}")
|
146
116
|
elif self.question.qtype == "MC":
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
class AboutDialog(QtWidgets.QMessageBox):
|
160
|
-
def __init__(self, parent: QtWidgets.QWidget) -> None:
|
117
|
+
for n, ans in enumerate(self.question._element.findall(XMLTags.ANSWER)):
|
118
|
+
self.ui.previewTextEdit.append(
|
119
|
+
f"<b>Answer {n + 1}, Fraction {ans.get('fraction')}:</b>"
|
120
|
+
)
|
121
|
+
self.ui.previewTextEdit.append(
|
122
|
+
self._replaceImgPlaceholder(ans.find("text").text)
|
123
|
+
)
|
124
|
+
|
125
|
+
|
126
|
+
class AboutDialog(QMessageBox):
|
127
|
+
def __init__(self, parent: QWidget) -> None:
|
161
128
|
super().__init__(parent)
|
162
129
|
self.setWindowTitle(f"About {e2mMetadata['name']}")
|
163
|
-
self.setIcon(
|
164
|
-
self.setStandardButtons(
|
130
|
+
self.setIcon(QMessageBox.Information)
|
131
|
+
self.setStandardButtons(QMessageBox.StandardButton.Close)
|
165
132
|
|
166
133
|
self.aboutMessage: str = f"""
|
167
134
|
<h1> About {e2mMetadata["name"]} v{e2mMetadata["version"]}</h1><br>
|
@@ -2,7 +2,7 @@ import logging
|
|
2
2
|
import math
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
|
-
from PySide6 import
|
5
|
+
from PySide6.QtWidgets import QWidget
|
6
6
|
|
7
7
|
from excel2moodle import mainLogger
|
8
8
|
from excel2moodle.core.question import ParametricQuestion
|
@@ -18,7 +18,7 @@ loggerSignal = LogWindowHandler()
|
|
18
18
|
mainLogger.addHandler(loggerSignal)
|
19
19
|
|
20
20
|
|
21
|
-
class EqCheckerWindow(
|
21
|
+
class EqCheckerWindow(QWidget):
|
22
22
|
def __init__(self) -> None:
|
23
23
|
super().__init__()
|
24
24
|
self.excelFile = Path()
|
excel2moodle/ui/treewidget.py
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
-
|
1
|
+
"""The `treewidget` Module provides the `QuestionItem` and the `CategoryItem` item.
|
2
|
+
|
3
|
+
Those two are subclasses of `QTreeWidgetItem`, to provide an easy interface
|
4
|
+
of accessing the corresponding questions from the items.
|
5
|
+
"""
|
6
|
+
|
2
7
|
from PySide6.QtCore import Qt
|
8
|
+
from PySide6.QtWidgets import QTreeWidgetItem
|
3
9
|
|
4
10
|
from excel2moodle.core.dataStructure import Category
|
5
11
|
from excel2moodle.core.question import ParametricQuestion, Question
|
6
12
|
|
7
13
|
|
8
|
-
class QuestionItem(
|
14
|
+
class QuestionItem(QTreeWidgetItem):
|
9
15
|
def __init__(self, parent, question: Question | ParametricQuestion) -> None:
|
10
16
|
super().__init__(parent)
|
11
17
|
self.setData(2, Qt.UserRole, question)
|
@@ -20,7 +26,7 @@ class QuestionItem(QtWidgets.QTreeWidgetItem):
|
|
20
26
|
return self.data(2, Qt.UserRole)
|
21
27
|
|
22
28
|
|
23
|
-
class CategoryItem(
|
29
|
+
class CategoryItem(QTreeWidgetItem):
|
24
30
|
def __init__(self, parent, category: Category) -> None:
|
25
31
|
super().__init__(parent)
|
26
32
|
self.setData(2, Qt.UserRole, category)
|
@@ -45,24 +51,3 @@ class CategoryItem(QtWidgets.QTreeWidgetItem):
|
|
45
51
|
|
46
52
|
def getCategory(self) -> Category:
|
47
53
|
return self.data(2, Qt.UserRole)
|
48
|
-
|
49
|
-
|
50
|
-
# class SpinBoxDelegate(QtWidgets.QStyledItemDelegate):
|
51
|
-
# def __init__(self, parent=None):
|
52
|
-
# super().__init__(parent)
|
53
|
-
#
|
54
|
-
# def createEditor(self, parent, option, index):
|
55
|
-
# # Create a QSpinBox when the item is being edited
|
56
|
-
# spinbox = QtWidgets.QSpinBox(parent)
|
57
|
-
# spinbox.setMinimum(0)
|
58
|
-
# spinbox.setMaximum(100)
|
59
|
-
# return spinbox
|
60
|
-
#
|
61
|
-
# def setEditorData(self, editor, index):
|
62
|
-
# # Set the current value of the QSpinBox based on the item's data
|
63
|
-
# value = index.model().data(index, Qt.EditRole)
|
64
|
-
# editor.setValue(value)
|
65
|
-
#
|
66
|
-
# def setModelData(self, editor, model, index):
|
67
|
-
# # When editing is done, update the model data with the QSpinBox value
|
68
|
-
# model.setData(index, editor.value(), Qt.EditRole)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: excel2moodle
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.1
|
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
|
@@ -12,11 +12,11 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Requires-Python: >=3.10
|
13
13
|
Description-Content-Type: text/markdown
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: pyside6>=6.8.0
|
16
15
|
Requires-Dist: pandas>=2.1.3
|
17
16
|
Requires-Dist: lxml>=5.4.0
|
18
17
|
Requires-Dist: asteval>=1.0.6
|
19
18
|
Requires-Dist: python-calamine>=0.3.2
|
19
|
+
Requires-Dist: pyside6-essentials>=6.8.0
|
20
20
|
Dynamic: license-file
|
21
21
|
|
22
22
|
# excel 2 Moodle
|
@@ -81,6 +81,38 @@ 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.6.1 (2025-07-12)
|
85
|
+
Fixing import error caused by dumping pyside meta package
|
86
|
+
|
87
|
+
### bugfix (1 change)
|
88
|
+
|
89
|
+
- [fixing pyside import error](https://gitlab.com/jbosse3/excel2moodle/-/commit/e5e0fc7695caa1a6864785828ff7311fa9624ad4)
|
90
|
+
|
91
|
+
## 0.6.0 (2025-07-12)
|
92
|
+
Added variable generator and other architechtural improvements
|
93
|
+
|
94
|
+
### documentation (1 change)
|
95
|
+
|
96
|
+
- [Documenting variable generator usage](https://gitlab.com/jbosse3/excel2moodle/-/commit/3e4d3019b29872b5cfddf5539d5ebe7638bca049)
|
97
|
+
|
98
|
+
### feature (5 changes)
|
99
|
+
|
100
|
+
- [Opening spreadsheet file works from within excel2moodle](https://gitlab.com/jbosse3/excel2moodle/-/commit/9470f12ea5f098745a3210b281a5144a938ae8b5)
|
101
|
+
- [Variables are copied to clipboard](https://gitlab.com/jbosse3/excel2moodle/-/commit/87a7e5ec75f899b293e89ad3c1742567e3ec1c29)
|
102
|
+
- [Removed dependence on pyside6-addons](https://gitlab.com/jbosse3/excel2moodle/-/commit/2b3a7cf48581c14bd9cb570cd61d1d41aa410e11)
|
103
|
+
- [Var Generator ready](https://gitlab.com/jbosse3/excel2moodle/-/commit/ea97f0639dc35a4c99a64ae3976ccc8a0ac5d109)
|
104
|
+
- [Merge development of BulletsObj, Parametrization and VarGenerator](https://gitlab.com/jbosse3/excel2moodle/-/commit/40b46f3c143e082f1bb985d6c8c4e68bb6b6a7a8)
|
105
|
+
|
106
|
+
### improvement (7 changes)
|
107
|
+
|
108
|
+
- [Adapted Param. Parser to use bullet Obj](https://gitlab.com/jbosse3/excel2moodle/-/commit/194cab7cc6aecb2d25d1cb9c1538ed7d607dd9e1)
|
109
|
+
- [Added bulleList Object](https://gitlab.com/jbosse3/excel2moodle/-/commit/4ea982b8d8dc270675d2cb059c59fa980ce38894)
|
110
|
+
- [Parametrics in beta stage](https://gitlab.com/jbosse3/excel2moodle/-/commit/7d04d8ef2fc603c1b12b6934c827ce079df5d540)
|
111
|
+
- [Refactor parse() method, to construct complete xml-Tree](https://gitlab.com/jbosse3/excel2moodle/-/commit/8dc4bea9aa0673d39357115254dd55b02c04114e)
|
112
|
+
- [Refactored question assembly to only update fields.](https://gitlab.com/jbosse3/excel2moodle/-/commit/d7accb69be3b4a1e65f59eeecfb463f2663fabd4)
|
113
|
+
- [Adapted NFM Question to parametricResult](https://gitlab.com/jbosse3/excel2moodle/-/commit/fe552cd2b538ca8886415c200e4a2a3ecc1fbb2f) ([merge request](https://gitlab.com/jbosse3/excel2moodle/-/merge_requests/5))
|
114
|
+
- [Implemented ParametricResult Object](https://gitlab.com/jbosse3/excel2moodle/-/commit/e36d025955f1cab8e0542d66263ab70e3d8980df) ([merge request](https://gitlab.com/jbosse3/excel2moodle/-/merge_requests/5))
|
115
|
+
|
84
116
|
## 0.5.2 (2025-06-30)
|
85
117
|
Extended Documentation and bugfix for import Module
|
86
118
|
|
@@ -1,38 +1,39 @@
|
|
1
1
|
excel2moodle/__init__.py,sha256=mnb-RWgmWIPSBk4S65a_jP6rxntAkTeYxN0ObUJalbQ,1801
|
2
|
-
excel2moodle/__main__.py,sha256=
|
2
|
+
excel2moodle/__main__.py,sha256=DgddHjnxFPXQp4CFvqGNjgUZRgaHp15U57Va9UdW_ms,1184
|
3
3
|
excel2moodle/logger.py,sha256=fq8ZOkCI1wj38v8IyrZsUlpt16onlSH_phqbVvYUwBQ,3725
|
4
4
|
excel2moodle/core/__init__.py,sha256=87BwhtZse72Tk17Ib-V9X2k9wkhmtVnEj2ZmJ9JBAnI,63
|
5
|
+
excel2moodle/core/bullets.py,sha256=F9g0dZfkMjoYQ5jneT47GwM1-RAyhPF8y203W5mOYpU,3505
|
5
6
|
excel2moodle/core/category.py,sha256=wLzpbweQbzaItdbp2NCPI_Zmk94fy1EDOwEEN8zPvkU,2123
|
6
|
-
excel2moodle/core/dataStructure.py,sha256=
|
7
|
+
excel2moodle/core/dataStructure.py,sha256=eMQAuTTKXLmE72I6MP1255BP15N-35Qb4bESyIy4K7w,16011
|
7
8
|
excel2moodle/core/etHelpers.py,sha256=G37qplp8tPJxqHNCBrf2Wo0jJZ0aDbxE9slQavqYqd8,2293
|
8
9
|
excel2moodle/core/exceptions.py,sha256=9xfsaIcm6Yej6QAZga0d3DK3jLQejdfgJARuAaG-uZY,739
|
9
|
-
excel2moodle/core/globals.py,sha256=
|
10
|
-
excel2moodle/core/
|
11
|
-
excel2moodle/core/
|
12
|
-
excel2moodle/core/question.py,sha256=_BSaMdDB269Q4Ag7izegMiExjYRTYOuhYe-qo92EAAg,11726
|
10
|
+
excel2moodle/core/globals.py,sha256=URrDtWUeaZvhv38ETk_MGRDZpWF0J5cTWO_Jpf0rS9E,2807
|
11
|
+
excel2moodle/core/parser.py,sha256=ExTgPkRXevXDcnQlWE2oGoEp6mbpzP1GBmzY4zs-OPU,7220
|
12
|
+
excel2moodle/core/question.py,sha256=pltXKN52tBpFoPKEt9cIdGzZzVKpCQcXun2g9Eu1ljE,13726
|
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
|
+
excel2moodle/extra/variableGenerator.py,sha256=fiXaTeFaC--1KRyMNcsjBgUlUZ8H1rGS6f_WenN60fQ,9851
|
18
19
|
excel2moodle/question_types/__init__.py,sha256=81mss0g7SVtnlb-WkydE28G_dEAAf6oT1uB8lpK2-II,1041
|
19
|
-
excel2moodle/question_types/cloze.py,sha256
|
20
|
+
excel2moodle/question_types/cloze.py,sha256=SvUS4gVKTr6z26KI17zV7pNIGIWQ2JdIVtPr-hgOOo8,13602
|
20
21
|
excel2moodle/question_types/mc.py,sha256=2kn6dPjFVg97H8SlUBFbcPjzDk84vgDGCMOtSABseu0,5225
|
21
22
|
excel2moodle/question_types/nf.py,sha256=bMP4IXrhnXmAI0NmjEc7DtX4xGaUbxzLicE2LjeaUho,1150
|
22
|
-
excel2moodle/question_types/nfm.py,sha256=
|
23
|
+
excel2moodle/question_types/nfm.py,sha256=p1yP6QWtFzQpVVfpjZ7R8GrsEtUSUPklbvjC8_VgYmQ,2734
|
23
24
|
excel2moodle/ui/UI_equationChecker.py,sha256=evQDlqCHeooJcAnYjhFCyjlPhfknr7ULGKQwMmqQeJ4,8947
|
24
25
|
excel2moodle/ui/UI_exportSettingsDialog.py,sha256=71xxXEqtewN0ReMfJ5t4gbrX_Bf0VEuxJ_DIV7ZtH94,6045
|
25
|
-
excel2moodle/ui/UI_mainWindow.py,sha256=
|
26
|
+
excel2moodle/ui/UI_mainWindow.py,sha256=9w8bRgOrVEX7BRGQvMuVhPCiSOsXYkMb4rxLDeRErII,21544
|
27
|
+
excel2moodle/ui/UI_variableGenerator.py,sha256=DjpZnBELSqyOjJdwjXNP2V71rCPm3tr6_XkNT9F3e34,11205
|
26
28
|
excel2moodle/ui/UI_variantDialog.py,sha256=snVaF3_YAc7NWjMRg7NzbjL_PzNbOpt4eiqElkE46io,5414
|
27
29
|
excel2moodle/ui/__init__.py,sha256=4EdGtpzwH3rgw4xW9E5x9kdPQYwKbo9rehHRZTNxCrQ,44
|
28
|
-
excel2moodle/ui/appUi.py,sha256=
|
29
|
-
excel2moodle/ui/dialogs.py,sha256=
|
30
|
-
excel2moodle/ui/equationChecker.py,sha256=
|
31
|
-
excel2moodle/ui/treewidget.py,sha256=
|
32
|
-
excel2moodle/
|
33
|
-
excel2moodle-0.
|
34
|
-
excel2moodle-0.
|
35
|
-
excel2moodle-0.
|
36
|
-
excel2moodle-0.
|
37
|
-
excel2moodle-0.
|
38
|
-
excel2moodle-0.5.2.dist-info/RECORD,,
|
30
|
+
excel2moodle/ui/appUi.py,sha256=0jxpgGiDU82AEDrj-BFgD9XdktrY_0EB7bAVMqWzJLY,13283
|
31
|
+
excel2moodle/ui/dialogs.py,sha256=8m1_hBGHt56T581PpPDsCrAd66cIy_Kxo_8NIy9kw_0,6179
|
32
|
+
excel2moodle/ui/equationChecker.py,sha256=RII9DlZAlHqe5udBWzeUaozhtyi3ZkCZs8h3-oO6pEw,2700
|
33
|
+
excel2moodle/ui/treewidget.py,sha256=aLR_B1iT2j1AAGk_9Dc6WtRbq1XijK8pICG8BEOW_Ak,1916
|
34
|
+
excel2moodle-0.6.1.dist-info/licenses/LICENSE,sha256=ywQqe6Sitymkf2lV2NRcx_aGsaC-KbSl_EfEsRXmNRw,35135
|
35
|
+
excel2moodle-0.6.1.dist-info/METADATA,sha256=963o72_grmJfUqLcnc4HQcbPXxOQ62BRtZGUCq6ku34,8656
|
36
|
+
excel2moodle-0.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
37
|
+
excel2moodle-0.6.1.dist-info/entry_points.txt,sha256=myfMLDThuGgWHMJDPPfILiZqo_7D3fhmDdJGqWOAjPw,60
|
38
|
+
excel2moodle-0.6.1.dist-info/top_level.txt,sha256=5V1xRUQ9o7UmOCmNoWCZPAuy5nXp3Qbzyqch8fUGT_c,13
|
39
|
+
excel2moodle-0.6.1.dist-info/RECORD,,
|
@@ -1,80 +0,0 @@
|
|
1
|
-
"""Numeric Multi Questions Module to calculate results from a formula.
|
2
|
-
|
3
|
-
This module calculates a series of results from al matrix of variables.
|
4
|
-
For each column in the matrix there will be one result.
|
5
|
-
As well it returns a bullet points string that shows the numerical values corresponding to the set of variables
|
6
|
-
"""
|
7
|
-
|
8
|
-
import re
|
9
|
-
|
10
|
-
import pandas as pd
|
11
|
-
from asteval import Interpreter
|
12
|
-
|
13
|
-
astEval = Interpreter()
|
14
|
-
|
15
|
-
|
16
|
-
def getVariablesDict(df: pd.DataFrame, keyList: list, index: int) -> dict:
|
17
|
-
"""Liest alle Variablen-Listen deren Name in ``keyList`` ist aus dem DataFrame im Column[index]."""
|
18
|
-
dic = {}
|
19
|
-
for k in keyList:
|
20
|
-
val = df.loc[str(k)][index]
|
21
|
-
if isinstance(val, str) and val is not None:
|
22
|
-
li = val.split(";")
|
23
|
-
dic[str(k)] = li
|
24
|
-
else:
|
25
|
-
dic[str(k)] = [str(val)]
|
26
|
-
return dic
|
27
|
-
|
28
|
-
|
29
|
-
def setParameters(parameters: dict, index: int) -> None:
|
30
|
-
"""Ubergibt die Parameter mit entsprechenden Variablen-Namen an den asteval-Interpreter.
|
31
|
-
|
32
|
-
Dann kann dieser die equation loesen.
|
33
|
-
"""
|
34
|
-
for k, v in parameters.items():
|
35
|
-
comma = re.compile(r",")
|
36
|
-
value = comma.sub(".", v[index])
|
37
|
-
astEval.symtable[k] = float(value)
|
38
|
-
|
39
|
-
|
40
|
-
def insertVariablesToBPoints(varDict: dict, bulletPoints: str, index: int) -> str:
|
41
|
-
"""Für jeden Eintrag im varDict, wird im bulletPoints String der
|
42
|
-
Substring "{key}" durch value[index] ersetzt.
|
43
|
-
"""
|
44
|
-
for k, v in varDict.items():
|
45
|
-
s = r"{" + str(k) + r"}"
|
46
|
-
matcher = re.compile(s)
|
47
|
-
bulletPoints = matcher.sub(str(v[index]), bulletPoints)
|
48
|
-
return bulletPoints
|
49
|
-
|
50
|
-
|
51
|
-
def getVarsList(bps: str) -> list:
|
52
|
-
"""Durchsucht den bulletPoints String nach den Variablen `{var}`."""
|
53
|
-
vars = re.findall(r"\{\w\}", str(bps))
|
54
|
-
variablen = []
|
55
|
-
for v in vars:
|
56
|
-
variablen.append(v.strip("{}"))
|
57
|
-
return variablen
|
58
|
-
|
59
|
-
|
60
|
-
def parseNumericMultiQuestion(
|
61
|
-
datFrame: pd.DataFrame,
|
62
|
-
bulletPoints: str,
|
63
|
-
equation: str,
|
64
|
-
questionIndex: int,
|
65
|
-
) -> tuple[list[str], list[float]]:
|
66
|
-
"""Berechnet die Ergebnisse anhand der Variablen in *bulletPoints*.
|
67
|
-
|
68
|
-
Gibt eine Liste mit allen Ergebnissen zurück
|
69
|
-
und eine Liste mit den bulletPoints-Strings, die die Numerischen Variablen enthalten
|
70
|
-
"""
|
71
|
-
results = []
|
72
|
-
bps = []
|
73
|
-
varNames = getVarsList(bulletPoints)
|
74
|
-
variables = getVariablesDict(datFrame, varNames, questionIndex)
|
75
|
-
length = len(next(iter(variables.values())))
|
76
|
-
for n in range(length):
|
77
|
-
setParameters(variables, n)
|
78
|
-
results.append(astEval(equation))
|
79
|
-
bps.append(insertVariablesToBPoints(variables, bulletPoints, n))
|
80
|
-
return bps, results
|