excel2moodle 0.3.7__py3-none-any.whl → 0.4.0__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/__init__.py CHANGED
@@ -25,13 +25,8 @@ Functionality
25
25
  """
26
26
 
27
27
  import logging
28
- import logging.config as logConfig
29
28
  from importlib import metadata
30
29
  from importlib.metadata import version
31
- from pathlib import Path
32
-
33
- from excel2moodle.logger import LogWindowHandler, loggerConfig
34
- from excel2moodle.ui.settings import Settings, SettingsKey
35
30
 
36
31
  try:
37
32
  __version__ = version("excel2moodle")
@@ -53,11 +48,11 @@ if __package__ is not None:
53
48
  }
54
49
 
55
50
 
56
- settings = Settings()
57
- logfile = Path(settings.get(SettingsKey.LOGFILE)).resolve()
58
- e2mMetadata["logfile"] = logfile
59
- if logfile.exists() and logfile.is_file():
60
- logfile.replace(f"{logfile}.old")
51
+ isMain: bool = False
61
52
 
62
53
  mainLogger = logging.getLogger(__name__)
63
- logConfig.dictConfig(config=loggerConfig)
54
+
55
+
56
+ def isMainState() -> bool:
57
+ mainLogger.debug("Returning mainState %s", isMain)
58
+ return isMain
excel2moodle/__main__.py CHANGED
@@ -1,13 +1,17 @@
1
1
  """Main Function to make the Package executable."""
2
2
 
3
+ import logging.config as logConfig
3
4
  import signal
5
+ from pathlib import Path
4
6
 
5
7
  from PySide6 import QtWidgets, sys
6
8
 
9
+ import excel2moodle
7
10
  from excel2moodle import e2mMetadata, mainLogger
8
11
  from excel2moodle.core import dataStructure
12
+ from excel2moodle.core.settings import Settings, SettingsKey
13
+ from excel2moodle.logger import loggerConfig
9
14
  from excel2moodle.ui import appUi as ui
10
- from excel2moodle.ui.settings import Settings
11
15
 
12
16
 
13
17
  def main() -> None:
@@ -25,4 +29,12 @@ def main() -> None:
25
29
 
26
30
 
27
31
  if __name__ == "__main__":
32
+ excel2moodle.isMain = True
33
+ settings = Settings()
34
+ logfile = Path(settings.get(SettingsKey.LOGFILE)).resolve()
35
+ e2mMetadata["logfile"] = logfile
36
+ if logfile.exists() and logfile.is_file():
37
+ logfile.replace(f"{logfile}.old")
38
+
39
+ logConfig.dictConfig(config=loggerConfig)
28
40
  main()
@@ -18,6 +18,7 @@ from excel2moodle.core.category import Category
18
18
  from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
19
19
  from excel2moodle.core.globals import DFIndex
20
20
  from excel2moodle.core.question import Question
21
+ from excel2moodle.core.settings import Settings, SettingsKey
21
22
  from excel2moodle.core.validator import Validator
22
23
  from excel2moodle.logger import LogAdapterQuestionID
23
24
  from excel2moodle.question_types import QuestionTypeMapping
@@ -25,7 +26,6 @@ from excel2moodle.question_types.mc import MCQuestion, MCQuestionParser
25
26
  from excel2moodle.question_types.nf import NFQuestion, NFQuestionParser
26
27
  from excel2moodle.question_types.nfm import NFMQuestion, NFMQuestionParser
27
28
  from excel2moodle.ui.dialogs import QuestionVariantDialog
28
- from excel2moodle.ui.settings import Settings, SettingsKey
29
29
  from excel2moodle.ui.treewidget import QuestionItem
30
30
 
31
31
  if TYPE_CHECKING:
@@ -93,10 +93,11 @@ class QuestionDB:
93
93
  self.categoriesMetaData = pd.read_excel(
94
94
  f,
95
95
  sheet_name=self.settings.get(SettingsKey.CATEGORIESSHEET),
96
- usecols=["Kategorie", "Beschreibung", "Punkte", "Version"],
97
96
  index_col=0,
98
97
  )
99
- logger.info("Sucessfully read categoriesMetaData")
98
+ logger.info(
99
+ "Sucessfully read categoriesMetaData \n %s", self.categoriesMetaData
100
+ )
100
101
 
101
102
  def _setProjectSettings(self, settings: pd.DataFrame) -> None:
102
103
  for tag, value in settings.iterrows():
@@ -173,14 +174,22 @@ class QuestionDB:
173
174
  """Setup the category from the ``dataframe``.
174
175
  :emits: categoryReady(self) Signal.
175
176
  """ # noqa: D401
176
- p = self.categoriesMetaData["Punkte"].iloc[categoryNumber - 1]
177
- points = p if not pd.isna(p) else self.settings.get(SettingsKey.POINTS)
178
- v = self.categoriesMetaData["Version"].iloc[categoryNumber - 1]
179
- version = v if not pd.isna(v) else self.settings.get(SettingsKey.VERSION)
177
+ points = (
178
+ self.categoriesMetaData["points"].iloc[categoryNumber - 1]
179
+ if "points" in self.categoriesMetaData
180
+ and not pd.isna(self.categoriesMetaData["points"]).iloc[categoryNumber - 1]
181
+ else self.settings.get(SettingsKey.POINTS)
182
+ )
183
+ version = (
184
+ self.categoriesMetaData["version"].iloc[categoryNumber - 1]
185
+ if "version" in self.categoriesMetaData
186
+ and not pd.isna(self.categoriesMetaData["version"].iloc[categoryNumber - 1])
187
+ else self.settings.get(SettingsKey.VERSION)
188
+ )
180
189
  category = Category(
181
190
  categoryNumber,
182
191
  categoryName,
183
- self.categoriesMetaData["Beschreibung"].iloc[categoryNumber - 1],
192
+ self.categoriesMetaData["description"].iloc[categoryNumber - 1],
184
193
  dataframe=categoryDf,
185
194
  points=points,
186
195
  version=version,
@@ -204,24 +213,19 @@ class QuestionDB:
204
213
  """
205
214
  for qNum in category.dataframe.columns:
206
215
  try:
207
- self.parseQuestion(category, qNum)
208
- except InvalidFieldException as e:
216
+ self.setupAndParseQuestion(category, qNum)
217
+ except (InvalidFieldException, QNotParsedException, ValueError) as e:
209
218
  logger.exception(
210
- "Question %s%02d is invalid.",
219
+ "Question %s%02d couldn't be parsed. The Question Data: \n %s",
211
220
  category.id,
212
221
  qNum,
213
- exc_info=e,
214
- )
215
- except QNotParsedException as e:
216
- logger.exception(
217
- "Frage %s konnte nicht erstellt werden",
218
- category.questions[qNum].id,
222
+ category.dataframe[qNum],
219
223
  exc_info=e,
220
224
  )
221
225
  self.signals.categoryQuestionsReady.emit(category)
222
226
 
223
227
  @classmethod
224
- def parseQuestion(cls, category: Category, qNumber: int) -> None:
228
+ def setupAndParseQuestion(cls, category: Category, qNumber: int) -> None:
225
229
  """Check if the Question Data is valid. Then parse it.
226
230
 
227
231
  The Question data is accessed from `category.dataframe` via its number
@@ -315,9 +319,9 @@ class QuestionDB:
315
319
  if dialog.exec() == QtWidgets.QDialog.Accepted:
316
320
  variant = dialog.variant
317
321
  logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
318
- q.assemble(variant)
319
322
  else:
320
323
  logger.warning("Keine Fragenvariante wurde gewählt.")
324
+ q.assemble(variant)
321
325
  else:
322
326
  q.assemble()
323
327
  tree.append(q.element)
@@ -17,7 +17,7 @@ class DFIndex(StrEnum):
17
17
  """
18
18
 
19
19
  TEXT = "text"
20
- BPOINTS = "bulletPoints"
20
+ BPOINTS = "bulletPoint"
21
21
  TRUE = "true"
22
22
  FALSE = "false"
23
23
  TYPE = "type"
@@ -2,7 +2,6 @@ import logging
2
2
  import re
3
3
 
4
4
  import lxml.etree as ET
5
- import pandas as pd
6
5
 
7
6
  import excel2moodle.core.etHelpers as eth
8
7
  from excel2moodle.core import stringHelpers
@@ -15,13 +14,11 @@ from excel2moodle.core.globals import (
15
14
  feedBElements,
16
15
  )
17
16
  from excel2moodle.core.question import Picture, Question
17
+ from excel2moodle.core.settings import Settings, SettingsKey
18
18
  from excel2moodle.logger import LogAdapterQuestionID
19
- from excel2moodle.ui.settings import Settings, SettingsKey
20
19
 
21
20
  loggerObj = logging.getLogger(__name__)
22
21
 
23
- settings = Settings()
24
-
25
22
 
26
23
  class QuestionParser:
27
24
  """Setup the Parser Object.
@@ -30,6 +27,8 @@ class QuestionParser:
30
27
  Important to implement the answers methods.
31
28
  """
32
29
 
30
+ settings = Settings()
31
+
33
32
  def __init__(self) -> None:
34
33
  """Initialize the general Question parser."""
35
34
  self.genFeedbacks: list[XMLTags] = []
@@ -48,27 +47,22 @@ class QuestionParser:
48
47
  """Create a ``Picture`` object ``question``if the question needs a pic."""
49
48
  if hasattr(self, "picture") and self.question.picture.ready:
50
49
  return True
51
- picKey = self.rawInput[DFIndex.PICTURE]
52
- svgFolder = settings.get(SettingsKey.PICTUREFOLDER)
53
- if picKey != 0 and pd.notna(picKey):
54
- if not hasattr(self.question, "picture"):
55
- self.question.picture = Picture(picKey, svgFolder, self.question)
56
- if self.question.picture.ready:
57
- return True
58
- return False
50
+ picKey = self.rawInput.get(DFIndex.PICTURE, False)
51
+ f = self.settings.get(SettingsKey.PICTUREFOLDER)
52
+ svgFolder = (f / self.question.katName).resolve()
53
+ if not hasattr(self.question, "picture"):
54
+ self.question.picture = Picture(picKey, svgFolder, self.question.id)
55
+ return bool(self.question.picture.ready)
59
56
 
60
57
  def setMainText(self) -> None:
61
58
  paragraphs: list[ET._Element] = [TextElements.PLEFT.create()]
62
59
  ET.SubElement(paragraphs[0], "b").text = f"ID {self.question.id}"
63
60
  text = self.rawInput[DFIndex.TEXT]
64
- pcount = 0
65
61
  for t in text:
66
- if not pd.isna(t):
67
- pcount += 1
68
- paragraphs.append(TextElements.PLEFT.create())
69
- paragraphs[-1].text = t
62
+ paragraphs.append(TextElements.PLEFT.create())
63
+ paragraphs[-1].text = t
70
64
  self.question.qtextParagraphs = paragraphs
71
- self.logger.debug("Created main Text with: %s paragraphs", pcount)
65
+ self.logger.debug("Created main Text with: %s paragraphs", len(text))
72
66
 
73
67
  def setBPoints(self) -> None:
74
68
  """If there bulletPoints are set in the Spreadsheet it creates an unordered List-Element in ``Question.bulletList``."""
@@ -194,7 +188,6 @@ class QuestionParser:
194
188
  def getNumericAnsElement(
195
189
  self,
196
190
  result: float,
197
- tolerance: float = 0,
198
191
  fraction: float = 100,
199
192
  format: str = "moodle_auto_format",
200
193
  ) -> ET.Element:
@@ -218,22 +211,15 @@ class QuestionParser:
218
211
  TextElements.SPANGREEN,
219
212
  ),
220
213
  )
221
- tolerance = self.getTolerancePercent(tolerance)
222
- tol = abs(round(result * (tolerance / 100), 3))
223
- ansEle.append(eth.getElement(XMLTags.TOLERANCE, text=str(tol)))
224
- return ansEle
225
-
226
- def getTolerancePercent(self, tolerance: float) -> int:
227
- """Get the correct tolerance.
228
- If ``tolerance < 1``: it is interpreted as the fraction.
229
- If ``tolerance >= 1``: it is interpreted as percentage.
230
- """
231
- if tolerance == 0 or pd.isna(tolerance) or tolerance >= 100:
232
- tolerance = settings.get(SettingsKey.PARSERNF_TOLERANCE)
214
+ tolerance = float(self.rawInput.get(DFIndex.TOLERANCE, 0))
215
+ if tolerance == 0 or tolerance >= 100:
216
+ tolerance = self.settings.get(SettingsKey.TOLERANCE)
233
217
  self.logger.info(
234
218
  "Using default tolerance %s percent from settings",
235
219
  tolerance,
236
220
  )
237
- tolerancePercent = 100 * tolerance if tolerance < 1 else tolerance
238
- self.logger.debug("Using tolerance %s percent", tolerancePercent)
239
- return int(tolerancePercent)
221
+ tolPercent = 100 * tolerance if tolerance < 1 else tolerance
222
+ self.logger.debug("Using tolerance %s percent", tolPercent)
223
+ relTolerance = abs(round(result * (tolerance / 100), 3))
224
+ ansEle.append(eth.getElement(XMLTags.TOLERANCE, text=str(relTolerance)))
225
+ return ansEle
@@ -16,9 +16,11 @@ from excel2moodle.core.globals import (
16
16
  XMLTags,
17
17
  questionTypes,
18
18
  )
19
+ from excel2moodle.core.settings import Settings, SettingsKey
19
20
  from excel2moodle.logger import LogAdapterQuestionID
20
21
 
21
22
  loggerObj = logging.getLogger(__name__)
23
+ settings = Settings()
22
24
 
23
25
 
24
26
  class Question:
@@ -68,15 +70,15 @@ class Question:
68
70
 
69
71
  def __repr__(self) -> str:
70
72
  li: list[str] = []
71
- li.append(f"Question v{self.category.version}")
72
- li.append(f"{self.id=}")
73
+ li.append(f"Question v{self.id}")
74
+ li.append(f"{self.qtype}")
73
75
  li.append(f"{self.parent=}")
74
- return "\n".join(li)
76
+ return "\t".join(li)
75
77
 
76
78
  def assemble(self, variant: int = 1) -> None:
77
79
  textElements: list[ET.Element] = []
78
80
  textElements.extend(self.qtextParagraphs)
79
- self.logger.debug("Starting assembly")
81
+ self.logger.debug("Starting assembly, (variant %s)", variant)
80
82
  if self.element is not None:
81
83
  mainText = self.element.find(XMLTags.QTEXT)
82
84
  self.logger.debug(f"found existing Text in element {mainText=}")
@@ -95,7 +97,6 @@ class Question:
95
97
  textElements.append(self.picture.htmlTag)
96
98
  mainText.append(self.picture.element)
97
99
  mainText.append(etHelpers.getCdatTxtElement(textElements))
98
- # self.element.insert(3, mainText)
99
100
  self.logger.debug("inserted MainText to element")
100
101
  if len(self.answerVariants) > 0:
101
102
  ans = self.element.find(XMLTags.ANSWER)
@@ -134,61 +135,94 @@ class Question:
134
135
 
135
136
 
136
137
  class Picture:
137
- def __init__(self, picKey: str, imgFolder: Path, question: Question) -> None:
138
- self.pic = picKey
138
+ def __init__(
139
+ self, picKey: str, imgFolder: Path, questionId: str, width: int = 0
140
+ ) -> None:
141
+ self.logger = LogAdapterQuestionID(loggerObj, {"qID": questionId})
142
+ self.picID: str
143
+ w: int = width if width > 0 else settings.get(SettingsKey.PICTUREWIDTH)
144
+ self.size: dict[str, str] = {"width": str(w)}
139
145
  self.ready: bool = False
140
- self.question = question
141
- self.logger = LogAdapterQuestionID(loggerObj, {"qID": self.question.id})
142
- self.imgFolder = (imgFolder / question.katName).resolve()
146
+ self.imgFolder = imgFolder
143
147
  self.htmlTag: ET.Element
144
148
  self.path: Path
145
- self._setPath()
146
- if hasattr(self, "picID"):
149
+ self.questionId: str = questionId
150
+ self.logger.debug("Instantiating a new picture in %s", picKey)
151
+ if self.getImgId(picKey):
147
152
  self.ready = self.__getImg()
148
-
149
- def _setPath(self) -> None:
150
- if self.pic == 1:
151
- self.picID = self.question.id
152
153
  else:
153
- selectedPic = self.pic[2:]
154
- self.logger.debug("Got the picture key: %s", selectedPic)
155
- try:
156
- self.picID = f"{self.question.category.id}{int(selectedPic):02d}"
157
- except ValueError as e:
158
- self.logger.warning(
159
- msg=f"Bild-ID konnte aus dem Key: {self.pic} nicht festgestellt werden",
160
- exc_info=e,
161
- )
162
-
163
- def _getBase64Img(self, imgPath):
164
- with open(imgPath, "rb") as img:
165
- return base64.b64encode(img.read()).decode("utf-8")
154
+ self.ready = False
155
+
156
+ def getImgId(self, imgKey: str) -> bool:
157
+ """Get the image ID and width based on the given key.
158
+ The key should either be the full ID (as the question) or only the question Num.
159
+ If only the number is given, the category.id is prepended.
160
+ The width should be specified by `ID:width:XX`. where xx is the px value.
161
+ """
162
+ width = re.findall(r"\:width\:(\d+)", str(imgKey))
163
+ height = re.findall(r"\:height\:(\d+)", str(imgKey))
164
+ if len(width) > 0 and width[0]:
165
+ self.size["width"] = width[0]
166
+ elif len(height) > 0 and height[0]:
167
+ self.size["height"] = height[0]
168
+ self.size.pop("width")
169
+ self.logger.debug("Size of picture is %s", self.size)
170
+ if imgKey in ("true", "True", "yes"):
171
+ self.picID = self.questionId
172
+ return True
173
+ num: list[int | str] = re.findall(r"^\d+", str(imgKey))
174
+ app: list[int | str] = re.findall(r"^\d+([A-Za-z_\-]+)", str(imgKey))
175
+ if imgKey in ("false", "nan", False) or len(num) == 0:
176
+ return False
177
+ imgID: int = int(num[0])
178
+ if imgID < 10:
179
+ picID = f"{self.questionId[:3]}{imgID:02d}"
180
+ elif imgID < 10000:
181
+ picID = f"{self.questionId[:1]}{imgID:04d}"
182
+ elif imgID <= 100000:
183
+ picID = str(imgID)
184
+ if len(app) > 0 and app[0]:
185
+ self.picID = f"{picID}{app[0]}"
186
+ else:
187
+ self.picID = str(picID)
188
+ self.logger.debug("Evaluated the imgID %s from %s", self.picID, imgKey)
189
+ return True
166
190
 
167
- def _setImgElement(self, dir: Path, picID: int) -> None:
168
- """Gibt das Bild im dirPath mit dir qID als base64 encodiert mit den entsprechenden XML-Tags zurück."""
169
- self.path: Path = (dir / str(picID)).with_suffix(".svg")
170
- self.element: ET.Element = ET.Element(
171
- "file",
172
- name=f"{self.path.name}",
173
- path="/",
174
- encoding="base64",
175
- )
176
- self.element.text = self._getBase64Img(self.path)
191
+ def _getBase64Img(self, imgPath: Path):
192
+ with imgPath.open("rb") as img:
193
+ return base64.b64encode(img.read()).decode("utf-8")
177
194
 
178
195
  def __getImg(self) -> bool:
196
+ suffixes = ["png", "svg", "jpeg", "jpg"]
197
+ paths = [
198
+ path
199
+ for suf in suffixes
200
+ for path in self.imgFolder.glob(f"{self.picID}.{suf}")
201
+ ]
202
+ self.logger.debug("Found the following paths %s", paths)
179
203
  try:
180
- self._setImgElement(self.imgFolder, int(self.picID))
181
- self.htmlTag = ET.Element(
182
- "img",
183
- src=f"@@PLUGINFILE@@/{self.path.name}",
184
- alt=f"Bild {self.path.name}",
185
- width="500",
186
- )
187
- return True
188
- except FileNotFoundError as e:
204
+ self.path = paths[0]
205
+ except IndexError as e:
206
+ msg = f"The Picture from key {self.picID} is not found"
207
+ # raise InvalidFieldException(msg, self.questionId, DFIndex.PICTURE)
189
208
  self.logger.warning(
190
209
  msg=f"Bild {self.picID} konnte nicht gefunden werden ",
191
210
  exc_info=e,
192
211
  )
193
212
  self.element = None
194
213
  return False
214
+ base64Img = self._getBase64Img(self.path)
215
+ self.element: ET.Element = ET.Element(
216
+ "file",
217
+ name=f"{self.path.name}",
218
+ path="/",
219
+ encoding="base64",
220
+ )
221
+ self.element.text = base64Img
222
+ self.htmlTag = ET.Element(
223
+ "img",
224
+ src=f"@@PLUGINFILE@@/{self.path.name}",
225
+ alt=f"Bild {self.path.name}",
226
+ **self.size,
227
+ )
228
+ return True
@@ -7,6 +7,8 @@ from typing import ClassVar, Literal, overload
7
7
 
8
8
  from PySide6.QtCore import QSettings, QTimer, Signal
9
9
 
10
+ import excel2moodle
11
+
10
12
  logger = logging.getLogger(__name__)
11
13
 
12
14
 
@@ -58,16 +60,18 @@ class SettingsKey(StrEnum):
58
60
  return self._typ_
59
61
 
60
62
  QUESTIONVARIANT = "defaultQuestionVariant", "testgen", int, 0
61
- INCLUDEINCATS = "includeCats", "tesgen", bool, False
62
- PARSERNF_TOLERANCE = "tolerance", "parser/nf", int, 1
63
- PICTURESUBFOLDER = "imgSubFolder", "core", str, "Abbildungen"
63
+ INCLUDEINCATS = "includeCats", "testgen", bool, False
64
+ TOLERANCE = "tolerance", "parser/nf", int, 1
64
65
  PICTUREFOLDER = "pictureFolder", "core", Path, None
65
66
  SPREADSHEETFOLDER = "spreadsheetFolder", "core", Path, None
66
67
  LOGLEVEL = "loglevel", "core", str, "INFO"
67
68
  LOGFILE = "logfile", "core", str, "excel2moodleLogFile.log"
68
- CATEGORIESSHEET = "categoriesDataSheet", "core", str, "Kategorien"
69
+ CATEGORIESSHEET = "categoriesSheet", "core", str, "Kategorien"
69
70
  VERSION = "version", "project", int, 1
70
71
  POINTS = "points", "project", float, 1.0
72
+ PICTURESUBFOLDER = "imgFolder", "project", str, "Abbildungen"
73
+ PICTUREWIDTH = "imgWidth", "project", int, 500
74
+ ANSPICWIDTH = "answerImgWidth", "project", int, 120
71
75
 
72
76
 
73
77
  class Settings(QSettings):
@@ -79,11 +83,12 @@ class Settings(QSettings):
79
83
  def __init__(self) -> None:
80
84
  """Instantiate the settings."""
81
85
  super().__init__("jbosse3", "excel2moodle")
82
- logger.info("Settings are stored under: %s", self.fileName())
83
- if self.contains(SettingsKey.SPREADSHEETFOLDER.full):
84
- self.sheet = self.get(SettingsKey.SPREADSHEETFOLDER)
85
- if self.sheet.is_file():
86
- QTimer.singleShot(300, self._emitSpreadsheetChanged)
86
+ if excel2moodle.isMainState():
87
+ logger.info("Settings are stored under: %s", self.fileName())
88
+ if self.contains(SettingsKey.SPREADSHEETFOLDER.full):
89
+ self.sheet = self.get(SettingsKey.SPREADSHEETFOLDER)
90
+ if self.sheet.is_file():
91
+ QTimer.singleShot(300, self._emitSpreadsheetChanged)
87
92
 
88
93
  def _emitSpreadsheetChanged(self) -> None:
89
94
  self.shPathChanged.emit(self.sheet)
@@ -93,9 +98,11 @@ class Settings(QSettings):
93
98
  self,
94
99
  key: Literal[
95
100
  SettingsKey.QUESTIONVARIANT,
96
- SettingsKey.PARSERNF_TOLERANCE,
101
+ SettingsKey.TOLERANCE,
97
102
  SettingsKey.VERSION,
98
103
  SettingsKey.POINTS,
104
+ SettingsKey.PICTUREWIDTH,
105
+ SettingsKey.ANSPICWIDTH,
99
106
  ],
100
107
  ) -> int: ...
101
108
  @overload
@@ -117,12 +124,18 @@ class Settings(QSettings):
117
124
  ) -> Path: ...
118
125
 
119
126
  def get(self, key: SettingsKey):
120
- """Get the typesafe settings value."""
121
- logger.debug("LocalSettings: %s", self.localSettings)
127
+ """Get the typesafe settings value.
128
+
129
+ If local Settings are stored, they are returned.
130
+ If no setting is made, the default value is returned.
131
+ """
122
132
  if key in self.localSettings:
123
133
  val = key.typ()(self.localSettings[key])
124
134
  logger.debug("Returning project setting: %s = %s", key, val)
125
135
  return val
136
+ if not excel2moodle.isMainState():
137
+ logger.warning("No GUI: Returning default value.")
138
+ return key.default
126
139
  if key.typ() is Path:
127
140
  path: Path = self.value(key.full, defaultValue=key.default)
128
141
  try:
@@ -158,9 +171,11 @@ class Settings(QSettings):
158
171
  local
159
172
  True saves local project specific settings.
160
173
  Defaults to False
161
- The local settings are meant to be set in the first sheet ``settings``
174
+ The local settings are meant to be set in the first sheet `settings`
162
175
 
163
176
  """
177
+ if not excel2moodle.isMainState():
178
+ local = True
164
179
  if local:
165
180
  if key in SettingsKey:
166
181
  self.localSettings[key] = value
@@ -4,14 +4,15 @@ import base64
4
4
  from pathlib import Path
5
5
 
6
6
  import lxml.etree as ET
7
+ from pandas import pandas
7
8
 
8
9
 
9
- def getListFromStr(stringList: str) -> list[str]:
10
+ def getListFromStr(stringList: str | list[str]) -> list[str]:
10
11
  """Get a python List of strings from a semi-colon separated string."""
11
12
  stripped: list[str] = []
12
- li = stringList.split(";")
13
+ li = stringList if isinstance(stringList, list) else stringList.split(";")
13
14
  for i in li:
14
- s = i.strip()
15
+ s = i.strip() if not pandas.isna(i) else None
15
16
  if s:
16
17
  stripped.append(s)
17
18
  return stripped
@@ -96,6 +96,8 @@ class Validator:
96
96
  for idx, val in self.df.items():
97
97
  if not isinstance(idx, str):
98
98
  continue
99
+ if pd.isna(val):
100
+ continue
99
101
  if idx in self.qdata:
100
102
  if isinstance(self.qdata[idx], list):
101
103
  self.qdata[idx].append(val)
@@ -124,16 +126,17 @@ class Validator:
124
126
  def _typeCheck(self) -> tuple[bool, list[DFIndex] | None]:
125
127
  invalid: list[DFIndex] = []
126
128
  for field, typ in self.mandatory.items():
127
- if isinstance(self.df[field], pd.Series):
129
+ if field in self.df and isinstance(self.df[field], pd.Series):
128
130
  for f in self.df[field]:
129
131
  if pd.notna(f) and not isinstance(f, typ):
130
132
  invalid.append(field)
131
133
  elif not isinstance(self.df[field], typ):
132
134
  invalid.append(field)
133
135
  for field, typ in self.optional.items():
134
- if field in self.df:
135
- if pd.notna(self.df[field]) and not isinstance(self.df[field], typ):
136
- invalid.append(field)
136
+ if field in self.df and isinstance(self.df[field], pd.Series):
137
+ for f in self.df[field]:
138
+ if pd.notna(f) and not isinstance(f, typ):
139
+ invalid.append(field)
137
140
  if len(invalid) == 0:
138
141
  return True, None
139
142
  return False, invalid
excel2moodle/logger.py CHANGED
@@ -8,7 +8,7 @@ import logging
8
8
 
9
9
  from PySide6.QtCore import QObject, Signal
10
10
 
11
- from excel2moodle.ui.settings import Settings, SettingsKey
11
+ from excel2moodle.core.settings import Settings, SettingsKey
12
12
 
13
13
  settings = Settings()
14
14