excel2moodle 0.6.2__tar.gz → 0.6.4__tar.gz
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-0.6.2 → excel2moodle-0.6.4}/PKG-INFO +28 -1
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/README.md +27 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/__init__.py +2 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/__main__.py +18 -3
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/bullets.py +5 -6
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/category.py +4 -3
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/dataStructure.py +29 -27
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/parser.py +19 -1
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/question.py +14 -6
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/settings.py +9 -2
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/validator.py +30 -20
- excel2moodle-0.6.4/excel2moodle/extra/updateQuery.py +48 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/extra/variableGenerator.py +73 -49
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/cloze.py +5 -6
- excel2moodle-0.6.4/excel2moodle/ui/UI_updateDlg.py +110 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/appUi.py +46 -18
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/dialogs.py +18 -1
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/treewidget.py +30 -10
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/PKG-INFO +28 -1
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/SOURCES.txt +2 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/pyproject.toml +11 -1
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_questionDataGet.py +6 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/LICENSE +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/__init__.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/etHelpers.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/exceptions.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/globals.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/stringHelpers.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/extra/__init__.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/extra/equationVerification.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/logger.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/__init__.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/mc.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/nf.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/nfm.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_equationChecker.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_exportSettingsDialog.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_mainWindow.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_variableGenerator.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_variantDialog.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/__init__.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/equationChecker.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/dependency_links.txt +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/entry_points.txt +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/requires.txt +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/top_level.txt +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/setup.cfg +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_bullets.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_feedbacking.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_nfmParsing.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_parseQuestion.py +0 -0
- {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_picture.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: excel2moodle
|
3
|
-
Version: 0.6.
|
3
|
+
Version: 0.6.4
|
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
|
@@ -90,6 +90,33 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
|
|
90
90
|
|
91
91
|
# Changelogs
|
92
92
|
|
93
|
+
## 0.6.4 (2025-09-02)
|
94
|
+
Added Scripted Media Support
|
95
|
+
|
96
|
+
### feature (1 change)
|
97
|
+
|
98
|
+
- [Added support for scripted Media content.](https://gitlab.com/jbosse3/excel2moodle/-/commit/2021942392147d0e9740af5286f469dd6226ffa5)
|
99
|
+
|
100
|
+
## 0.6.3 (2025-08-03)
|
101
|
+
Lots of small improvements made
|
102
|
+
|
103
|
+
### improvement (3 changes)
|
104
|
+
|
105
|
+
- [small logging improvements and error handling](https://gitlab.com/jbosse3/excel2moodle/-/commit/149f8e923a06d9d7077fe90c7005a3e1d5d2d42f)
|
106
|
+
- [Make variable generator rules editable](https://gitlab.com/jbosse3/excel2moodle/-/commit/80ea32d97bdec16b77100bc870a0e0272a739dd4)
|
107
|
+
- [Variable generator only generates unique sets.](https://gitlab.com/jbosse3/excel2moodle/-/commit/d347c91bbac66de1da157fee4f76faf8d4636557)
|
108
|
+
|
109
|
+
### bugfix (3 changes)
|
110
|
+
|
111
|
+
- [mixed parametric and non parametric Bullets are working now](https://gitlab.com/jbosse3/excel2moodle/-/commit/f094b13dffd4b6b7ac1a03fc7e34eec6e8d1bfa7)
|
112
|
+
- [Loglevel setting is respected in spreadsheet file](https://gitlab.com/jbosse3/excel2moodle/-/commit/d6ef89beeec94f24782a00b7564883074badf72d)
|
113
|
+
- [Treewidget variants count updated after variable generation](https://gitlab.com/jbosse3/excel2moodle/-/commit/c48a0d093a0cce85fd3e9c3c091eef936739c02b)
|
114
|
+
|
115
|
+
### feature (2 changes)
|
116
|
+
|
117
|
+
- [Category ID taken from any number in its name](https://gitlab.com/jbosse3/excel2moodle/-/commit/ac7e19af5f25ac2e576b63c478e7b07153e782ef)
|
118
|
+
- [Implemented Update Check on Startup](https://gitlab.com/jbosse3/excel2moodle/-/commit/a143edd47f566c5e731c05612f4ac21dc7728eb7)
|
119
|
+
|
93
120
|
## 0.6.2 (2025-08-02)
|
94
121
|
Adding export options and fixing cloze points bug
|
95
122
|
|
@@ -68,6 +68,33 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
|
|
68
68
|
|
69
69
|
# Changelogs
|
70
70
|
|
71
|
+
## 0.6.4 (2025-09-02)
|
72
|
+
Added Scripted Media Support
|
73
|
+
|
74
|
+
### feature (1 change)
|
75
|
+
|
76
|
+
- [Added support for scripted Media content.](https://gitlab.com/jbosse3/excel2moodle/-/commit/2021942392147d0e9740af5286f469dd6226ffa5)
|
77
|
+
|
78
|
+
## 0.6.3 (2025-08-03)
|
79
|
+
Lots of small improvements made
|
80
|
+
|
81
|
+
### improvement (3 changes)
|
82
|
+
|
83
|
+
- [small logging improvements and error handling](https://gitlab.com/jbosse3/excel2moodle/-/commit/149f8e923a06d9d7077fe90c7005a3e1d5d2d42f)
|
84
|
+
- [Make variable generator rules editable](https://gitlab.com/jbosse3/excel2moodle/-/commit/80ea32d97bdec16b77100bc870a0e0272a739dd4)
|
85
|
+
- [Variable generator only generates unique sets.](https://gitlab.com/jbosse3/excel2moodle/-/commit/d347c91bbac66de1da157fee4f76faf8d4636557)
|
86
|
+
|
87
|
+
### bugfix (3 changes)
|
88
|
+
|
89
|
+
- [mixed parametric and non parametric Bullets are working now](https://gitlab.com/jbosse3/excel2moodle/-/commit/f094b13dffd4b6b7ac1a03fc7e34eec6e8d1bfa7)
|
90
|
+
- [Loglevel setting is respected in spreadsheet file](https://gitlab.com/jbosse3/excel2moodle/-/commit/d6ef89beeec94f24782a00b7564883074badf72d)
|
91
|
+
- [Treewidget variants count updated after variable generation](https://gitlab.com/jbosse3/excel2moodle/-/commit/c48a0d093a0cce85fd3e9c3c091eef936739c02b)
|
92
|
+
|
93
|
+
### feature (2 changes)
|
94
|
+
|
95
|
+
- [Category ID taken from any number in its name](https://gitlab.com/jbosse3/excel2moodle/-/commit/ac7e19af5f25ac2e576b63c478e7b07153e782ef)
|
96
|
+
- [Implemented Update Check on Startup](https://gitlab.com/jbosse3/excel2moodle/-/commit/a143edd47f566c5e731c05612f4ac21dc7728eb7)
|
97
|
+
|
71
98
|
## 0.6.2 (2025-08-02)
|
72
99
|
Adding export options and fixing cloze points bug
|
73
100
|
|
@@ -45,6 +45,8 @@ if __package__ is not None:
|
|
45
45
|
"documentation": "https://jbosse3.gitlab.io/excel2moodle",
|
46
46
|
"homepage": meta["project-url"].split()[1],
|
47
47
|
"issues": "https://gitlab.com/jbosse3/excel2moodle/issues",
|
48
|
+
"funding": "https://ko-fi.com/jbosse3",
|
49
|
+
"API_id": "jbosse3%2Fexcel2moodle",
|
48
50
|
}
|
49
51
|
|
50
52
|
|
@@ -5,17 +5,20 @@ import signal
|
|
5
5
|
import sys
|
6
6
|
from pathlib import Path
|
7
7
|
|
8
|
+
from PySide6.QtCore import QTimer
|
8
9
|
from PySide6.QtWidgets import QApplication
|
9
10
|
|
10
11
|
import excel2moodle
|
11
|
-
from excel2moodle import e2mMetadata, mainLogger
|
12
|
+
from excel2moodle import __version__, e2mMetadata, mainLogger
|
12
13
|
from excel2moodle.core import dataStructure
|
13
14
|
from excel2moodle.core.settings import Settings, Tags
|
15
|
+
from excel2moodle.extra import updateQuery
|
14
16
|
from excel2moodle.logger import loggerConfig
|
15
17
|
from excel2moodle.ui import appUi as ui
|
16
18
|
|
17
19
|
|
18
20
|
def main() -> None:
|
21
|
+
app = QApplication(sys.argv)
|
19
22
|
excel2moodle.isMain = True
|
20
23
|
settings = Settings()
|
21
24
|
logfile = Path(settings.get(Tags.LOGFILE)).resolve()
|
@@ -24,12 +27,24 @@ def main() -> None:
|
|
24
27
|
logfile.replace(f"{logfile}.old")
|
25
28
|
logConfig.dictConfig(config=loggerConfig)
|
26
29
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
27
|
-
app = QApplication(sys.argv)
|
28
|
-
settings = Settings()
|
29
30
|
database: dataStructure.QuestionDB = dataStructure.QuestionDB(settings)
|
30
31
|
window = ui.MainWindow(settings, database)
|
31
32
|
database.window = window
|
32
33
|
window.show()
|
34
|
+
# Update check
|
35
|
+
latestTag = updateQuery.get_latest_tag(e2mMetadata.get("API_id"))
|
36
|
+
if latestTag is None:
|
37
|
+
mainLogger.warning(
|
38
|
+
"Couldn't check for Updates, maybe there is no internet connection"
|
39
|
+
)
|
40
|
+
if latestTag is not None and latestTag.strip("v") != __version__:
|
41
|
+
mainLogger.warning(
|
42
|
+
"A new Update is available: %s --> %s ", __version__, latestTag
|
43
|
+
)
|
44
|
+
changelog = updateQuery.get_changelog(e2mMetadata.get("API_id"))
|
45
|
+
# Delay showing the update dialog slightly
|
46
|
+
QTimer.singleShot(100, lambda: window.showUpdateDialog(changelog, latestTag))
|
47
|
+
|
33
48
|
for k, v in e2mMetadata.items():
|
34
49
|
msg = f"{k:^14s}: {v}"
|
35
50
|
mainLogger.info(msg)
|
@@ -5,7 +5,7 @@ import lxml.etree as ET
|
|
5
5
|
|
6
6
|
from excel2moodle.core import stringHelpers
|
7
7
|
from excel2moodle.core.globals import TextElements
|
8
|
-
from excel2moodle.core.question import ParametricQuestion
|
8
|
+
from excel2moodle.core.question import ParametricQuestion, Parametrics
|
9
9
|
from excel2moodle.logger import LogAdapterQuestionID
|
10
10
|
|
11
11
|
loggerObj = logging.getLogger(__name__)
|
@@ -15,14 +15,13 @@ class BulletList:
|
|
15
15
|
def __init__(self, rawBullets: list[str], qID: str) -> None:
|
16
16
|
self.rawBullets: list[str] = rawBullets
|
17
17
|
self.element: ET.Element = ET.Element("ul")
|
18
|
-
self.bullets: dict[str, BulletP] = {}
|
18
|
+
self.bullets: dict[str | int, BulletP] = {}
|
19
19
|
self.id = qID
|
20
20
|
self.logger = LogAdapterQuestionID(loggerObj, {"qID": self.id})
|
21
21
|
self._setupBullets(rawBullets)
|
22
22
|
|
23
|
-
def
|
24
|
-
|
25
|
-
) -> None:
|
23
|
+
def update(self, parametrics: Parametrics, variant: int = 1) -> None:
|
24
|
+
variables: dict[str, list[float]] = parametrics.variables
|
26
25
|
for var, bullet in self.bullets.items():
|
27
26
|
bullet.update(value=variables[var][variant - 1])
|
28
27
|
|
@@ -70,7 +69,7 @@ class BulletList:
|
|
70
69
|
if match is None:
|
71
70
|
self.logger.debug("Got a normal bulletItem")
|
72
71
|
num: float = float(quant.replace(",", "."))
|
73
|
-
bulletName
|
72
|
+
bulletName = i + 1
|
74
73
|
else:
|
75
74
|
bulletName = match.group(1)
|
76
75
|
num: float = 0.0
|
@@ -24,8 +24,9 @@ class Category:
|
|
24
24
|
) -> None:
|
25
25
|
"""Instantiate a new Category object."""
|
26
26
|
self.NAME = name
|
27
|
-
match = re.search(r"\d
|
28
|
-
|
27
|
+
match = re.search(r"\d+", str(self.NAME))
|
28
|
+
n = int(match.group(0)) if match else 99
|
29
|
+
self.n: int = n if n <= 99 and n >= 0 else 99
|
29
30
|
self.desc = str(description)
|
30
31
|
self.dataframe: pd.DataFrame = dataframe
|
31
32
|
self.settings: dict[str, float | str] = settings if settings else {}
|
@@ -52,7 +53,7 @@ class Category:
|
|
52
53
|
@property
|
53
54
|
def questions(self) -> dict:
|
54
55
|
if not hasattr(self, "_questions"):
|
55
|
-
msg = "Category
|
56
|
+
msg = f"Category {self.id} doesn't contain any valid questions."
|
56
57
|
raise ValueError(msg)
|
57
58
|
return self._questions
|
58
59
|
|
@@ -105,39 +105,29 @@ class QuestionDB:
|
|
105
105
|
When there is no 'seetings' worksheet in the file.
|
106
106
|
InvalidFieldException
|
107
107
|
When the settings are invalid
|
108
|
+
Or When the categories Sheet doesn't provide the necessary keys.
|
108
109
|
|
109
110
|
Before raising it logges the exceptions with a meaningful message.
|
110
111
|
|
111
112
|
"""
|
112
113
|
sheetPath = sheetPath if sheetPath else self.spreadsheet
|
113
114
|
logger.info("Start Parsing the Excel Metadata Sheet\n")
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
)
|
122
|
-
except ValueError:
|
123
|
-
logger.exception(
|
124
|
-
"Did you forget to specify a 'settings' sheet in the file?"
|
115
|
+
with Path(sheetPath).open("rb") as f:
|
116
|
+
settingDf = pd.read_excel(
|
117
|
+
f,
|
118
|
+
sheet_name="settings",
|
119
|
+
index_col=0,
|
120
|
+
header=None,
|
121
|
+
engine="calamine",
|
125
122
|
)
|
126
|
-
raise
|
127
123
|
logger.debug("Found the settings: \n\t%s", settingDf)
|
128
124
|
settingDf = self.harmonizeDFIndex(settingDf)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
125
|
+
settingsDict = Validator.dfToDict(settingDf[1])
|
126
|
+
Validator.listify(settingsDict)
|
127
|
+
for tag, value in settingsDict.items():
|
128
|
+
self.settings.set(tag, value)
|
133
129
|
|
134
|
-
|
135
|
-
self._validateProjectSettings(sheetPath=sheetPath)
|
136
|
-
except InvalidFieldException:
|
137
|
-
logger.exception(
|
138
|
-
"Can not create the database with invalid project settings."
|
139
|
-
)
|
140
|
-
raise
|
130
|
+
self._validateProjectSettings(sheetPath=sheetPath)
|
141
131
|
with Path(sheetPath).open("rb") as f:
|
142
132
|
self.categoriesMetaData = pd.read_excel(
|
143
133
|
f,
|
@@ -145,10 +135,20 @@ class QuestionDB:
|
|
145
135
|
index_col=0,
|
146
136
|
engine="calamine",
|
147
137
|
)
|
148
|
-
|
138
|
+
if "description" not in self.categoriesMetaData.columns:
|
139
|
+
msg = f"You need to specify the 'description' tag for each category in the sheet '{self.settings.get(Tags.CATEGORIESSHEET)}'."
|
140
|
+
raise InvalidFieldException(msg, "0000", "description")
|
141
|
+
logger.info("Sucessfully read categoriesMetaData")
|
149
142
|
return self.categoriesMetaData
|
150
143
|
|
151
144
|
def _validateProjectSettings(self, sheetPath: Path) -> None:
|
145
|
+
if Tags.LOGLEVEL in self.settings:
|
146
|
+
level: str = self.settings.get(Tags.LOGLEVEL)
|
147
|
+
if level.upper() not in ("DEBUG", "INFO", "WARNING", "ERROR"):
|
148
|
+
self.settings.pop(Tags.LOGLEVEL)
|
149
|
+
logger.warning("You specified an unsupported Loglevel: %s", level)
|
150
|
+
if self.window is not None:
|
151
|
+
self.window.logHandler.setLevel(self.settings.get(Tags.LOGLEVEL).upper())
|
152
152
|
if Tags.IMPORTMODULE in self.settings:
|
153
153
|
logger.warning(
|
154
154
|
"Appending: %s to sys.path. All names defined by it will be usable",
|
@@ -297,7 +297,7 @@ class QuestionDB:
|
|
297
297
|
for qNum in category.dataframe.columns:
|
298
298
|
try:
|
299
299
|
self.setupAndParseQuestion(category, qNum)
|
300
|
-
except (InvalidFieldException, QNotParsedException):
|
300
|
+
except (InvalidFieldException, QNotParsedException, AttributeError):
|
301
301
|
logger.exception(
|
302
302
|
"Question %s%02d couldn't be parsed. The Question Data: \n %s",
|
303
303
|
category.id,
|
@@ -379,10 +379,10 @@ class QuestionDB:
|
|
379
379
|
catdict: dict[Category, list[Question]] = {}
|
380
380
|
for q in questions:
|
381
381
|
logger.debug(f"got a question to append {q=}")
|
382
|
-
cat = q.parent().
|
382
|
+
cat = q.parent().category
|
383
383
|
if cat not in catdict:
|
384
384
|
catdict[cat] = []
|
385
|
-
catdict[cat].append(q.
|
385
|
+
catdict[cat].append(q.question)
|
386
386
|
for cat, qlist in catdict.items():
|
387
387
|
self._appendQElements(
|
388
388
|
cat,
|
@@ -423,6 +423,8 @@ class QuestionDB:
|
|
423
423
|
else:
|
424
424
|
logger.warning("Keine Fragenvariante wurde gewählt.")
|
425
425
|
tree.append(q.getUpdatedElement(variant=variant))
|
426
|
+
else:
|
427
|
+
tree.append(q.getUpdatedElement(variant=variant))
|
426
428
|
self._exportedQuestions.append(q)
|
427
429
|
|
428
430
|
def generateExportReport(
|
@@ -11,8 +11,9 @@ from excel2moodle.core.globals import (
|
|
11
11
|
XMLTags,
|
12
12
|
feedBElements,
|
13
13
|
)
|
14
|
-
from excel2moodle.core.question import Picture, Question
|
14
|
+
from excel2moodle.core.question import ParametricQuestion, Picture, Question
|
15
15
|
from excel2moodle.core.settings import Settings, Tags
|
16
|
+
from excel2moodle.extra.scriptCaller import MediaCall
|
16
17
|
from excel2moodle.logger import LogAdapterQuestionID
|
17
18
|
|
18
19
|
loggerObj = logging.getLogger(__name__)
|
@@ -129,9 +130,13 @@ class QuestionParser:
|
|
129
130
|
)
|
130
131
|
self.htmlRoot.append(bullets.element)
|
131
132
|
self.question.bulletList = bullets
|
133
|
+
if isinstance(self.question, ParametricQuestion):
|
134
|
+
self.question.updateQue = [bullets]
|
132
135
|
if self.hasPicture():
|
133
136
|
self.htmlRoot.append(self.question.picture.htmlTag)
|
134
137
|
textRootElem.append(self.question.picture.element)
|
138
|
+
if Tags.MEDIACALL in self.rawInput:
|
139
|
+
self.insertScriptedMedia()
|
135
140
|
ansList = self._parseAnswers()
|
136
141
|
if ansList is not None:
|
137
142
|
for ele in ansList:
|
@@ -166,6 +171,19 @@ class QuestionParser:
|
|
166
171
|
ele.append(eth.getCdatTxtElement(par))
|
167
172
|
return ele
|
168
173
|
|
174
|
+
def insertScriptedMedia(self) -> None:
|
175
|
+
"""Load the scripts, insert the div and call a Function."""
|
176
|
+
for script in self.rawInput.get(Tags.MEDIASCRIPTS):
|
177
|
+
ET.SubElement(
|
178
|
+
self.htmlRoot, "script", type="text/javascript", src=script
|
179
|
+
).text = ""
|
180
|
+
divId = f"scriptedMedia-{self.question.id}"
|
181
|
+
ET.SubElement(self.htmlRoot, "div", id=divId).text = ""
|
182
|
+
scriptCall = MediaCall(self.rawInput.get(Tags.MEDIACALL), divId=divId)
|
183
|
+
self.htmlRoot.append(scriptCall.element)
|
184
|
+
if isinstance(self.question, ParametricQuestion):
|
185
|
+
self.question.updateQue.append(scriptCall)
|
186
|
+
|
169
187
|
def _parseAnswers(self) -> list[ET.Element] | None:
|
170
188
|
"""Needs to be implemented in the type-specific subclasses."""
|
171
189
|
return None
|
@@ -53,7 +53,14 @@ class QuestionData(dict):
|
|
53
53
|
@overload
|
54
54
|
def get(
|
55
55
|
self,
|
56
|
-
key: Literal[
|
56
|
+
key: Literal[
|
57
|
+
Tags.BPOINTS,
|
58
|
+
Tags.TRUE,
|
59
|
+
Tags.FALSE,
|
60
|
+
Tags.QUESTIONPART,
|
61
|
+
Tags.TEXT,
|
62
|
+
Tags.MEDIASCRIPTS,
|
63
|
+
],
|
57
64
|
default: object = None,
|
58
65
|
) -> list: ...
|
59
66
|
@overload
|
@@ -115,6 +122,7 @@ class Question:
|
|
115
122
|
}
|
116
123
|
optionalTags: ClassVar[dict[Tags, type | UnionType]] = {
|
117
124
|
Tags.PICTURE: int | str,
|
125
|
+
Tags.MEDIACALL: list,
|
118
126
|
}
|
119
127
|
|
120
128
|
def __init_subclass__(cls, **kwargs) -> None:
|
@@ -202,6 +210,7 @@ class ParametricQuestion(Question):
|
|
202
210
|
self.rules: list[str] = []
|
203
211
|
self.parametrics: Parametrics
|
204
212
|
self._variant: int
|
213
|
+
self.updateQue: list
|
205
214
|
|
206
215
|
@property
|
207
216
|
def currentVariant(self) -> int:
|
@@ -214,13 +223,12 @@ class ParametricQuestion(Question):
|
|
214
223
|
`Question` returns the Element.
|
215
224
|
|
216
225
|
"""
|
217
|
-
if not hasattr(self, "
|
218
|
-
msg = "Can't assemble a parametric question, without the
|
226
|
+
if not hasattr(self, "updateQue"):
|
227
|
+
msg = "Can't assemble a parametric question, without the updateQue"
|
219
228
|
raise QNotParsedException(msg, self.id)
|
220
229
|
|
221
|
-
self.
|
222
|
-
|
223
|
-
)
|
230
|
+
for obj in self.updateQue:
|
231
|
+
obj.update(parametrics=self.parametrics, variant=variant)
|
224
232
|
self._variant = variant
|
225
233
|
return super().getUpdatedElement(variant)
|
226
234
|
|
@@ -96,9 +96,12 @@ class Tags(StrEnum):
|
|
96
96
|
PCORRECFB = "partialcorrectfeedback", str, "Your answer is partially right."
|
97
97
|
GENERALFB = "feedback", str, "You answered this question."
|
98
98
|
|
99
|
+
MEDIASCRIPTS = "mediascripts", list, None
|
100
|
+
MEDIACALL = "parametricmedia", str, None
|
101
|
+
|
99
102
|
|
100
103
|
class Settings:
|
101
|
-
values: ClassVar[dict[str, str | float | Path]] = {}
|
104
|
+
values: ClassVar[dict[str, str | float | Path | list]] = {}
|
102
105
|
|
103
106
|
def __contains__(self, tag: Tags) -> bool:
|
104
107
|
return bool(tag in type(self).values)
|
@@ -107,6 +110,10 @@ class Settings:
|
|
107
110
|
def clear(cls) -> None:
|
108
111
|
cls.values.clear()
|
109
112
|
|
113
|
+
@classmethod
|
114
|
+
def pop(cls, key: str):
|
115
|
+
return cls.values.pop(key)
|
116
|
+
|
110
117
|
@overload
|
111
118
|
@classmethod
|
112
119
|
def get(
|
@@ -160,7 +167,7 @@ class Settings:
|
|
160
167
|
default = key.default
|
161
168
|
if default is None:
|
162
169
|
return None
|
163
|
-
logger.
|
170
|
+
logger.debug("Returning the default value for %s", key)
|
164
171
|
return default
|
165
172
|
if key.typ() is Path:
|
166
173
|
path: Path = Path(raw)
|
@@ -54,31 +54,29 @@ class Validator:
|
|
54
54
|
if missing is not None:
|
55
55
|
raise InvalidFieldException(msg, qid, missing)
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
@staticmethod
|
58
|
+
def dfToDict(df: pd.Series) -> dict[str, int | float | list[str] | str]:
|
59
|
+
"""Convert a dataframe to dictionary, preserving lists."""
|
60
|
+
dic: dict[str, int | float | list[str] | str] = {}
|
61
|
+
for var, val in df.items():
|
62
|
+
if not isinstance(var, str):
|
62
63
|
continue
|
63
64
|
if pd.isna(val):
|
64
65
|
continue
|
65
|
-
if
|
66
|
-
if isinstance(
|
67
|
-
|
66
|
+
if var in dic:
|
67
|
+
if isinstance(dic[var], list):
|
68
|
+
dic[var].append(val)
|
68
69
|
else:
|
69
|
-
existing =
|
70
|
-
|
70
|
+
existing = dic[var]
|
71
|
+
dic[var] = [existing, val]
|
71
72
|
else:
|
72
|
-
|
73
|
-
return
|
74
|
-
|
75
|
-
def
|
76
|
-
"""
|
77
|
-
|
78
|
-
|
79
|
-
for key in self.qdata:
|
80
|
-
if key.startswith(tag) and not isinstance(self.qdata[key], list):
|
81
|
-
self.qdata[key] = stringHelpers.getListFromStr(self.qdata[key])
|
73
|
+
dic[var] = val
|
74
|
+
return dic
|
75
|
+
|
76
|
+
def getQuestionData(self) -> QuestionData:
|
77
|
+
"""Get the data from the spreadsheet as a dictionary."""
|
78
|
+
self.qdata = self.dfToDict(self.df)
|
79
|
+
self.listify(self.qdata)
|
82
80
|
tol = float(self.qdata.get(Tags.TOLERANCE, 0))
|
83
81
|
if tol < 0 or tol > 99:
|
84
82
|
tol = settings.get(Tags.TOLERANCE)
|
@@ -89,6 +87,18 @@ class Validator:
|
|
89
87
|
self.qdata[Tags.EQUATION] = str(self.qdata[Tags.RESULT])
|
90
88
|
return QuestionData(self.qdata)
|
91
89
|
|
90
|
+
@staticmethod
|
91
|
+
def listify(dictionary: dict) -> None:
|
92
|
+
"""Converts to list all tag values, which are supposed to be a list."""
|
93
|
+
for key in dictionary:
|
94
|
+
k: str = key.split(":")[0]
|
95
|
+
if k in Tags:
|
96
|
+
tag = Tags(k)
|
97
|
+
logger.info("Got the Tag %s from key: %s", tag, key)
|
98
|
+
if tag.typ() is list and not isinstance(dictionary[key], list):
|
99
|
+
dictionary[key] = stringHelpers.getListFromStr(dictionary[key])
|
100
|
+
logger.info("Converted Input to list for %s", key)
|
101
|
+
|
92
102
|
def _mandatory(self) -> tuple[bool, Tags | None]:
|
93
103
|
"""Detects if all keys of mandatory are filled with values."""
|
94
104
|
checker = pd.Series.notna(self.df)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""This module provides functions to query the GitLab API for project information."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import sys
|
5
|
+
import urllib.request
|
6
|
+
|
7
|
+
|
8
|
+
def get_latest_tag(project_id: str) -> str | None:
|
9
|
+
"""Queries the GitLab API for the latest tag of a project.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
project_id: The URL-encoded project ID (e.g., 'jbosse3%2Fexcel2moodle').
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
The name of the latest tag.
|
16
|
+
|
17
|
+
"""
|
18
|
+
url = f"https://gitlab.com/api/v4/projects/{project_id}/repository/tags"
|
19
|
+
try:
|
20
|
+
with urllib.request.urlopen(url) as response:
|
21
|
+
if response.status == 200:
|
22
|
+
data = json.loads(response.read().decode())
|
23
|
+
if data:
|
24
|
+
return data[0]["name"]
|
25
|
+
except urllib.error.URLError as e:
|
26
|
+
print(f"Error fetching latest tag: {e}", file=sys.stderr)
|
27
|
+
return None
|
28
|
+
|
29
|
+
|
30
|
+
def get_changelog(project_id: str, branch: str = "master") -> str:
|
31
|
+
"""Queries the GitLab API for the content of the CHANGELOG.md file.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
project_id: The URL-encoded project ID (e.g., 'jbosse3%2Fexcel2moodle').
|
35
|
+
branch: The branch to get the file from.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
The content of the CHANGELOG.md file.
|
39
|
+
|
40
|
+
"""
|
41
|
+
url = f"https://gitlab.com/api/v4/projects/{project_id}/repository/files/CHANGELOG.md/raw?ref={branch}"
|
42
|
+
try:
|
43
|
+
with urllib.request.urlopen(url) as response:
|
44
|
+
if response.status == 200:
|
45
|
+
return response.read().decode()
|
46
|
+
except urllib.error.URLError as e:
|
47
|
+
print(f"Error fetching changelog: {e}", file=sys.stderr)
|
48
|
+
return ""
|