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.
Files changed (52) hide show
  1. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/PKG-INFO +28 -1
  2. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/README.md +27 -0
  3. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/__init__.py +2 -0
  4. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/__main__.py +18 -3
  5. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/bullets.py +5 -6
  6. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/category.py +4 -3
  7. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/dataStructure.py +29 -27
  8. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/parser.py +19 -1
  9. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/question.py +14 -6
  10. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/settings.py +9 -2
  11. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/validator.py +30 -20
  12. excel2moodle-0.6.4/excel2moodle/extra/updateQuery.py +48 -0
  13. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/extra/variableGenerator.py +73 -49
  14. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/cloze.py +5 -6
  15. excel2moodle-0.6.4/excel2moodle/ui/UI_updateDlg.py +110 -0
  16. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/appUi.py +46 -18
  17. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/dialogs.py +18 -1
  18. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/treewidget.py +30 -10
  19. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/PKG-INFO +28 -1
  20. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/SOURCES.txt +2 -0
  21. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/pyproject.toml +11 -1
  22. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_questionDataGet.py +6 -0
  23. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/LICENSE +0 -0
  24. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/__init__.py +0 -0
  25. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/etHelpers.py +0 -0
  26. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/exceptions.py +0 -0
  27. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/globals.py +0 -0
  28. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/core/stringHelpers.py +0 -0
  29. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/extra/__init__.py +0 -0
  30. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/extra/equationVerification.py +0 -0
  31. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/logger.py +0 -0
  32. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/__init__.py +0 -0
  33. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/mc.py +0 -0
  34. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/nf.py +0 -0
  35. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/question_types/nfm.py +0 -0
  36. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_equationChecker.py +0 -0
  37. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_exportSettingsDialog.py +0 -0
  38. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_mainWindow.py +0 -0
  39. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_variableGenerator.py +0 -0
  40. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/UI_variantDialog.py +0 -0
  41. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/__init__.py +0 -0
  42. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle/ui/equationChecker.py +0 -0
  43. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/dependency_links.txt +0 -0
  44. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/entry_points.txt +0 -0
  45. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/requires.txt +0 -0
  46. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/excel2moodle.egg-info/top_level.txt +0 -0
  47. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/setup.cfg +0 -0
  48. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_bullets.py +0 -0
  49. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_feedbacking.py +0 -0
  50. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_nfmParsing.py +0 -0
  51. {excel2moodle-0.6.2 → excel2moodle-0.6.4}/test/test_parseQuestion.py +0 -0
  52. {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.2
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 updateBullets(
24
- self, variables: dict[str, list[float]], variant: int = 1
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: str = str(i + 1)
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+$", str(self.NAME))
28
- self.n: int = int(match.group(0)) if match else 99
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 question are not yet initialized"
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
- try:
115
- with Path(sheetPath).open("rb") as f:
116
- settingDf = pd.read_excel(
117
- f,
118
- sheet_name="settings",
119
- index_col=0,
120
- engine="calamine",
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
- for tag, value in settingDf.iterrows():
130
- val = value.iloc[0]
131
- if pd.notna(val):
132
- self.settings.set(tag, val)
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
- try:
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
- logger.info("Sucessfully read categoriesMetaData")
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().getCategory()
382
+ cat = q.parent().category
383
383
  if cat not in catdict:
384
384
  catdict[cat] = []
385
- catdict[cat].append(q.getQuestion())
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[Tags.BPOINTS, Tags.TRUE, Tags.FALSE, Tags.QUESTIONPART, Tags.TEXT],
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, "bulletList"):
218
- msg = "Can't assemble a parametric question, without the bulletPoints variables"
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.bulletList.updateBullets(
222
- variables=self.parametrics.variables, variant=variant
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.info("Returning the default value for %s", key)
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
- def getQuestionData(self) -> QuestionData:
58
- """Get the data from the spreadsheet as a dictionary."""
59
- self.qdata: dict[str, int | float | list[str] | str] = {}
60
- for idx, val in self.df.items():
61
- if not isinstance(idx, str):
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 idx in self.qdata:
66
- if isinstance(self.qdata[idx], list):
67
- self.qdata[idx].append(val)
66
+ if var in dic:
67
+ if isinstance(dic[var], list):
68
+ dic[var].append(val)
68
69
  else:
69
- existing = self.qdata[idx]
70
- self.qdata[idx] = [existing, val]
70
+ existing = dic[var]
71
+ dic[var] = [existing, val]
71
72
  else:
72
- self.qdata[idx] = val
73
- return self.formatQData()
74
-
75
- def formatQData(self) -> QuestionData:
76
- """Format the dictionary to The types for QuestionData."""
77
- listTags = (Tags.BPOINTS, Tags.TRUE, Tags.FALSE, Tags.TEXT, Tags.QUESTIONPART)
78
- for tag in listTags:
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 ""