excel2moodle 0.6.1__tar.gz → 0.6.3__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 (53) hide show
  1. {excel2moodle-0.6.1/excel2moodle.egg-info → excel2moodle-0.6.3}/PKG-INFO +66 -19
  2. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/README.md +64 -18
  3. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/__init__.py +2 -0
  4. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/__main__.py +18 -3
  5. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/bullets.py +2 -2
  6. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/category.py +27 -8
  7. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/dataStructure.py +122 -32
  8. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/etHelpers.py +0 -20
  9. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/globals.py +0 -9
  10. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/parser.py +21 -13
  11. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/question.py +43 -23
  12. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/settings.py +16 -5
  13. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/extra/equationVerification.py +0 -2
  14. excel2moodle-0.6.3/excel2moodle/extra/updateQuery.py +48 -0
  15. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/extra/variableGenerator.py +73 -49
  16. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/cloze.py +119 -87
  17. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/mc.py +15 -10
  18. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/nf.py +7 -1
  19. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/nfm.py +14 -10
  20. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_exportSettingsDialog.py +60 -14
  21. excel2moodle-0.6.3/excel2moodle/ui/UI_updateDlg.py +106 -0
  22. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/appUi.py +66 -24
  23. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/dialogs.py +30 -1
  24. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/treewidget.py +30 -10
  25. {excel2moodle-0.6.1 → excel2moodle-0.6.3/excel2moodle.egg-info}/PKG-INFO +66 -19
  26. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/SOURCES.txt +3 -1
  27. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/requires.txt +1 -0
  28. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/pyproject.toml +12 -2
  29. excel2moodle-0.6.3/test/test_feedbacking.py +73 -0
  30. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_nfmParsing.py +41 -12
  31. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_parseQuestion.py +0 -14
  32. excel2moodle-0.6.1/MANIFEST.in +0 -1
  33. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/LICENSE +0 -0
  34. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/__init__.py +0 -0
  35. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/exceptions.py +0 -0
  36. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/stringHelpers.py +0 -0
  37. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/validator.py +0 -0
  38. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/extra/__init__.py +0 -0
  39. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/logger.py +0 -0
  40. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/__init__.py +0 -0
  41. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_equationChecker.py +0 -0
  42. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_mainWindow.py +0 -0
  43. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_variableGenerator.py +0 -0
  44. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_variantDialog.py +0 -0
  45. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/__init__.py +0 -0
  46. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/equationChecker.py +0 -0
  47. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/dependency_links.txt +0 -0
  48. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/entry_points.txt +0 -0
  49. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/top_level.txt +0 -0
  50. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/setup.cfg +0 -0
  51. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_bullets.py +0 -0
  52. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_picture.py +0 -0
  53. {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_questionDataGet.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: excel2moodle
3
- Version: 0.6.1
3
+ Version: 0.6.3
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
@@ -17,16 +17,20 @@ Requires-Dist: lxml>=5.4.0
17
17
  Requires-Dist: asteval>=1.0.6
18
18
  Requires-Dist: python-calamine>=0.3.2
19
19
  Requires-Dist: pyside6-essentials>=6.8.0
20
+ Requires-Dist: pyyaml>=6.0.2
20
21
  Dynamic: license-file
21
22
 
22
23
  # excel 2 Moodle
23
- ![Logo](excel2moodleLogo.png "Logo excel2moodle"){width=35%}
24
+ ![Logo](https://gitlab.com/jbosse3/-/blob/master/excel2moodleLogo.png "Logo excel2moodle"){width=35%}
24
25
 
25
- This Python program helps to create Moodle questions in less time.
26
- The idea is to write the questions data into a spreadsheet file, from which the program generates the moodle compliant xml Files.
27
- All questions or a selection of questions can be exported into one xml file to be imported into moodle.
26
+ *excel2moodle* helps to create Moodle questions in less time.
27
+ The idea is to write the questions data into a spreadsheet file, from which the *excel2moodle* generates moodle compliant xml Files.
28
+ All questions or a selection of questions can then be imported into moodle.
28
29
 
29
30
  ## Concept
31
+ At the heart the *excel2moodle* is a simple key-value-pair "syntax", where the key is set once in the first spreadsheet column.
32
+ Each key can be provided with a value for each question in its column
33
+ To enhance reusability, key values which are correct for more than one question can be set per category or for all questions.
30
34
  The concept is, to store the different questions into categories of similar types and difficulties of questions, for each of which, a separated sheet in the Spreadsheet document should be created.
31
35
 
32
36
  A `settings` sheet contains global settings to be used for all questions and categories.
@@ -41,33 +45,38 @@ If you already have python and uv installed, it is as easy as running `uv tool i
41
45
 
42
46
  ### [ Documentation ](https://jbosse3.gitlab.io/excel2moodle/index.html)
43
47
  Once excel2moodle is installed you can checkout the [example question sheet](https://gitlab.com/jbosse3/excel2moodle/-/tree/master/example?ref_type=heads)
44
- in the repository.
48
+ in the repository. You need to download all Files in the `example` directory and save them together.
45
49
 
46
- Some steps are already documented as [ tutorials ](https://jbosse3.gitlab.io/excel2moodle/howto.html)
50
+ Most steps are already documented as [ tutorials ](https://jbosse3.gitlab.io/excel2moodle/howto.html)
47
51
  you can follow along.
48
52
 
49
53
  And please have a look into the [**user Reference**](https://jbosse3.gitlab.io/excel2moodle/userReference.html)
50
54
  of the documentation.
51
- That part explains each part of defining a question.
55
+ That part explains in more detail each part of defining a question.
52
56
 
53
57
 
54
- ## Functionality
55
- * Equation Verification:
56
- + this tool helps you to validate the correct equation for the parametrized Questions.
57
- * Question Preview:
58
+ ## Features
59
+ - Fully parametrized numeric questions:
60
+ * Formulas for the calculated results can be coded into extensive python modules, which can be loaded.
61
+ - Question Preview:
58
62
  + This helps you when selecting the correct questions for the export.
59
- * Export Options:
63
+ - Equation Verification:
64
+ + this tool helps you to validate the correct equation for the parametrized Questions.
65
+ - Variable Generation:
66
+ + You can generate variables for the parametric Question to easily create hundreds of different variants of the same question.
67
+ - Export Options:
60
68
  + you can export the questions preserving the categories in moodle
61
69
 
62
70
  ### Question Types
63
- * Generate multiple Choice Questions:
64
- + The answers can be pictures or normal text
65
- * Generate Numeric Questions
66
- * Generate parametrized numeric Questions
67
- * Generate parametrized cloze Questions
71
+ - Generate multiple Choice Questions:
72
+ * The answers can be pictures or normal text
73
+ - Generate Numeric Questions
74
+ - Generate parametrized numeric Questions:
75
+ * With the parametrization *excel2moodle* calculats the numeric answer from a given formula based on a set of variables.
76
+ - Generate parametrized cloze Questions
68
77
 
69
78
 
70
- ![MainWindow](mainWindow.png "Logo excel2moodle"){width=80%}
79
+ ![MainWindow](https://gitlab.com/jbosse3/-/blob/master/mainWindow.png "Logo excel2moodle"){width=80%}
71
80
 
72
81
  ## Licensing and authorship
73
82
  excel2moodle is lincensed under the latest [GNU GPL license](https://gitlab.com/jbosse3/excel2moodle/-/blob/master/LICENSE)
@@ -81,6 +90,44 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
81
90
 
82
91
  # Changelogs
83
92
 
93
+ ## 0.6.3 (2025-08-03)
94
+ Lots of small improvements made
95
+
96
+ ### improvement (3 changes)
97
+
98
+ - [small logging improvements and error handling](https://gitlab.com/jbosse3/excel2moodle/-/commit/149f8e923a06d9d7077fe90c7005a3e1d5d2d42f)
99
+ - [Make variable generator rules editable](https://gitlab.com/jbosse3/excel2moodle/-/commit/80ea32d97bdec16b77100bc870a0e0272a739dd4)
100
+ - [Variable generator only generates unique sets.](https://gitlab.com/jbosse3/excel2moodle/-/commit/d347c91bbac66de1da157fee4f76faf8d4636557)
101
+
102
+ ### bugfix (3 changes)
103
+
104
+ - [mixed parametric and non parametric Bullets are working now](https://gitlab.com/jbosse3/excel2moodle/-/commit/f094b13dffd4b6b7ac1a03fc7e34eec6e8d1bfa7)
105
+ - [Loglevel setting is respected in spreadsheet file](https://gitlab.com/jbosse3/excel2moodle/-/commit/d6ef89beeec94f24782a00b7564883074badf72d)
106
+ - [Treewidget variants count updated after variable generation](https://gitlab.com/jbosse3/excel2moodle/-/commit/c48a0d093a0cce85fd3e9c3c091eef936739c02b)
107
+
108
+ ### feature (2 changes)
109
+
110
+ - [Category ID taken from any number in its name](https://gitlab.com/jbosse3/excel2moodle/-/commit/ac7e19af5f25ac2e576b63c478e7b07153e782ef)
111
+ - [Implemented Update Check on Startup](https://gitlab.com/jbosse3/excel2moodle/-/commit/a143edd47f566c5e731c05612f4ac21dc7728eb7)
112
+
113
+ ## 0.6.2 (2025-08-02)
114
+ Adding export options and fixing cloze points bug
115
+
116
+ ### feature (4 changes)
117
+
118
+ - [Added export options to include all Question Variants and generate report](https://gitlab.com/jbosse3/excel2moodle/-/commit/6433615de23174451748b69669a9dce748dd5b4d)
119
+ - [Implemented export dialog generator method](https://gitlab.com/jbosse3/excel2moodle/-/commit/a8eda982309bf9a6dae7ef2b261a59654f2c8910)
120
+ - [Answer Feedback strings settable in sheet](https://gitlab.com/jbosse3/excel2moodle/-/commit/ad90da49ac60e429ad3243f54846b08f0caf5bc7)
121
+ - [Inverted result and feedback for NFM & Cloze questions](https://gitlab.com/jbosse3/excel2moodle/-/commit/57d77c83a661398b0082f84d25e5447000df9096)
122
+
123
+ ### improvement (1 change)
124
+
125
+ - [Missing `settings` sheet raises an error](https://gitlab.com/jbosse3/excel2moodle/-/commit/e1cc42c1d31981bf74582b23c24c6ac378e9256d)
126
+
127
+ ### bugfix (1 change)
128
+
129
+ - [resolve cloze moodle import error due to float points](https://gitlab.com/jbosse3/excel2moodle/-/commit/f13b7b9df39df55d65b6063a9deb1fc1c72f5ebb)
130
+
84
131
  ## 0.6.1 (2025-07-12)
85
132
  Fixing import error caused by dumping pyside meta package
86
133
 
@@ -1,11 +1,14 @@
1
1
  # excel 2 Moodle
2
- ![Logo](excel2moodleLogo.png "Logo excel2moodle"){width=35%}
2
+ ![Logo](https://gitlab.com/jbosse3/-/blob/master/excel2moodleLogo.png "Logo excel2moodle"){width=35%}
3
3
 
4
- This Python program helps to create Moodle questions in less time.
5
- The idea is to write the questions data into a spreadsheet file, from which the program generates the moodle compliant xml Files.
6
- All questions or a selection of questions can be exported into one xml file to be imported into moodle.
4
+ *excel2moodle* helps to create Moodle questions in less time.
5
+ The idea is to write the questions data into a spreadsheet file, from which the *excel2moodle* generates moodle compliant xml Files.
6
+ All questions or a selection of questions can then be imported into moodle.
7
7
 
8
8
  ## Concept
9
+ At the heart the *excel2moodle* is a simple key-value-pair "syntax", where the key is set once in the first spreadsheet column.
10
+ Each key can be provided with a value for each question in its column
11
+ To enhance reusability, key values which are correct for more than one question can be set per category or for all questions.
9
12
  The concept is, to store the different questions into categories of similar types and difficulties of questions, for each of which, a separated sheet in the Spreadsheet document should be created.
10
13
 
11
14
  A `settings` sheet contains global settings to be used for all questions and categories.
@@ -20,33 +23,38 @@ If you already have python and uv installed, it is as easy as running `uv tool i
20
23
 
21
24
  ### [ Documentation ](https://jbosse3.gitlab.io/excel2moodle/index.html)
22
25
  Once excel2moodle is installed you can checkout the [example question sheet](https://gitlab.com/jbosse3/excel2moodle/-/tree/master/example?ref_type=heads)
23
- in the repository.
26
+ in the repository. You need to download all Files in the `example` directory and save them together.
24
27
 
25
- Some steps are already documented as [ tutorials ](https://jbosse3.gitlab.io/excel2moodle/howto.html)
28
+ Most steps are already documented as [ tutorials ](https://jbosse3.gitlab.io/excel2moodle/howto.html)
26
29
  you can follow along.
27
30
 
28
31
  And please have a look into the [**user Reference**](https://jbosse3.gitlab.io/excel2moodle/userReference.html)
29
32
  of the documentation.
30
- That part explains each part of defining a question.
33
+ That part explains in more detail each part of defining a question.
31
34
 
32
35
 
33
- ## Functionality
34
- * Equation Verification:
35
- + this tool helps you to validate the correct equation for the parametrized Questions.
36
- * Question Preview:
36
+ ## Features
37
+ - Fully parametrized numeric questions:
38
+ * Formulas for the calculated results can be coded into extensive python modules, which can be loaded.
39
+ - Question Preview:
37
40
  + This helps you when selecting the correct questions for the export.
38
- * Export Options:
41
+ - Equation Verification:
42
+ + this tool helps you to validate the correct equation for the parametrized Questions.
43
+ - Variable Generation:
44
+ + You can generate variables for the parametric Question to easily create hundreds of different variants of the same question.
45
+ - Export Options:
39
46
  + you can export the questions preserving the categories in moodle
40
47
 
41
48
  ### Question Types
42
- * Generate multiple Choice Questions:
43
- + The answers can be pictures or normal text
44
- * Generate Numeric Questions
45
- * Generate parametrized numeric Questions
46
- * Generate parametrized cloze Questions
49
+ - Generate multiple Choice Questions:
50
+ * The answers can be pictures or normal text
51
+ - Generate Numeric Questions
52
+ - Generate parametrized numeric Questions:
53
+ * With the parametrization *excel2moodle* calculats the numeric answer from a given formula based on a set of variables.
54
+ - Generate parametrized cloze Questions
47
55
 
48
56
 
49
- ![MainWindow](mainWindow.png "Logo excel2moodle"){width=80%}
57
+ ![MainWindow](https://gitlab.com/jbosse3/-/blob/master/mainWindow.png "Logo excel2moodle"){width=80%}
50
58
 
51
59
  ## Licensing and authorship
52
60
  excel2moodle is lincensed under the latest [GNU GPL license](https://gitlab.com/jbosse3/excel2moodle/-/blob/master/LICENSE)
@@ -60,6 +68,44 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
60
68
 
61
69
  # Changelogs
62
70
 
71
+ ## 0.6.3 (2025-08-03)
72
+ Lots of small improvements made
73
+
74
+ ### improvement (3 changes)
75
+
76
+ - [small logging improvements and error handling](https://gitlab.com/jbosse3/excel2moodle/-/commit/149f8e923a06d9d7077fe90c7005a3e1d5d2d42f)
77
+ - [Make variable generator rules editable](https://gitlab.com/jbosse3/excel2moodle/-/commit/80ea32d97bdec16b77100bc870a0e0272a739dd4)
78
+ - [Variable generator only generates unique sets.](https://gitlab.com/jbosse3/excel2moodle/-/commit/d347c91bbac66de1da157fee4f76faf8d4636557)
79
+
80
+ ### bugfix (3 changes)
81
+
82
+ - [mixed parametric and non parametric Bullets are working now](https://gitlab.com/jbosse3/excel2moodle/-/commit/f094b13dffd4b6b7ac1a03fc7e34eec6e8d1bfa7)
83
+ - [Loglevel setting is respected in spreadsheet file](https://gitlab.com/jbosse3/excel2moodle/-/commit/d6ef89beeec94f24782a00b7564883074badf72d)
84
+ - [Treewidget variants count updated after variable generation](https://gitlab.com/jbosse3/excel2moodle/-/commit/c48a0d093a0cce85fd3e9c3c091eef936739c02b)
85
+
86
+ ### feature (2 changes)
87
+
88
+ - [Category ID taken from any number in its name](https://gitlab.com/jbosse3/excel2moodle/-/commit/ac7e19af5f25ac2e576b63c478e7b07153e782ef)
89
+ - [Implemented Update Check on Startup](https://gitlab.com/jbosse3/excel2moodle/-/commit/a143edd47f566c5e731c05612f4ac21dc7728eb7)
90
+
91
+ ## 0.6.2 (2025-08-02)
92
+ Adding export options and fixing cloze points bug
93
+
94
+ ### feature (4 changes)
95
+
96
+ - [Added export options to include all Question Variants and generate report](https://gitlab.com/jbosse3/excel2moodle/-/commit/6433615de23174451748b69669a9dce748dd5b4d)
97
+ - [Implemented export dialog generator method](https://gitlab.com/jbosse3/excel2moodle/-/commit/a8eda982309bf9a6dae7ef2b261a59654f2c8910)
98
+ - [Answer Feedback strings settable in sheet](https://gitlab.com/jbosse3/excel2moodle/-/commit/ad90da49ac60e429ad3243f54846b08f0caf5bc7)
99
+ - [Inverted result and feedback for NFM & Cloze questions](https://gitlab.com/jbosse3/excel2moodle/-/commit/57d77c83a661398b0082f84d25e5447000df9096)
100
+
101
+ ### improvement (1 change)
102
+
103
+ - [Missing `settings` sheet raises an error](https://gitlab.com/jbosse3/excel2moodle/-/commit/e1cc42c1d31981bf74582b23c24c6ac378e9256d)
104
+
105
+ ### bugfix (1 change)
106
+
107
+ - [resolve cloze moodle import error due to float points](https://gitlab.com/jbosse3/excel2moodle/-/commit/f13b7b9df39df55d65b6063a9deb1fc1c72f5ebb)
108
+
63
109
  ## 0.6.1 (2025-07-12)
64
110
  Fixing import error caused by dumping pyside meta package
65
111
 
@@ -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)
@@ -15,7 +15,7 @@ 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)
@@ -70,7 +70,7 @@ class BulletList:
70
70
  if match is None:
71
71
  self.logger.debug("Got a normal bulletItem")
72
72
  num: float = float(quant.replace(",", "."))
73
- bulletName: str = str(i + 1)
73
+ bulletName = i + 1
74
74
  else:
75
75
  bulletName = match.group(1)
76
76
  num: float = 0.0
@@ -5,10 +5,9 @@ from typing import TYPE_CHECKING
5
5
  import lxml.etree as ET
6
6
  import pandas as pd
7
7
 
8
- from excel2moodle.core.settings import Tags
9
-
10
8
  if TYPE_CHECKING:
11
9
  from excel2moodle.core.question import Question
10
+ from excel2moodle.core.settings import Tags
12
11
 
13
12
  loggerObj = logging.getLogger(__name__)
14
13
 
@@ -25,12 +24,13 @@ class Category:
25
24
  ) -> None:
26
25
  """Instantiate a new Category object."""
27
26
  self.NAME = name
28
- match = re.search(r"\d+$", str(self.NAME))
29
- 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
30
30
  self.desc = str(description)
31
31
  self.dataframe: pd.DataFrame = dataframe
32
32
  self.settings: dict[str, float | str] = settings if settings else {}
33
- self.questions: dict[int, Question] = {}
33
+ self._questions: dict[int, Question]
34
34
  self.maxVariants: int | None = None
35
35
  loggerObj.info("initializing Category %s", self.NAME)
36
36
 
@@ -50,6 +50,18 @@ class Category:
50
50
  def id(self) -> str:
51
51
  return f"{self.n:02d}"
52
52
 
53
+ @property
54
+ def questions(self) -> dict:
55
+ if not hasattr(self, "_questions"):
56
+ msg = f"Category {self.id} doesn't contain any valid questions."
57
+ raise ValueError(msg)
58
+ return self._questions
59
+
60
+ def appendQuestion(self, questionNumber: int, question) -> None:
61
+ if not hasattr(self, "_questions"):
62
+ self._questions: dict[int, Question] = {}
63
+ self._questions[questionNumber] = question
64
+
53
65
  def __hash__(self) -> int:
54
66
  return hash(self.NAME)
55
67
 
@@ -58,13 +70,20 @@ class Category:
58
70
  return self.NAME == other.NAME
59
71
  return False
60
72
 
61
- def getCategoryHeader(self) -> ET.Element:
73
+ def getCategoryHeader(self, subCategory: str | None = None) -> ET.Element:
62
74
  """Insert an <question type='category'> before all Questions of this Category."""
63
75
  header = ET.Element("question", type="category")
64
76
  cat = ET.SubElement(header, "category")
65
77
  info = ET.SubElement(header, "info", format="html")
66
- ET.SubElement(cat, "text").text = f"$module$/top/{self.NAME}"
78
+ catStr = (
79
+ f"$module$/top/{self.NAME}"
80
+ if subCategory is None
81
+ else f"$module$/top/{self.NAME}/Question-{subCategory[2:]}_Variants"
82
+ )
83
+ ET.SubElement(cat, "text").text = catStr
67
84
  ET.SubElement(info, "text").text = str(self.desc)
68
- ET.SubElement(header, "idnumber").text = self.id
85
+ ET.SubElement(header, "idnumber").text = (
86
+ f"cat-{self.id}" if subCategory is None else f"variants-{subCategory}"
87
+ )
69
88
  ET.indent(header)
70
89
  return header
@@ -3,6 +3,7 @@
3
3
  At the heart is the class ``xmlTest``
4
4
  """
5
5
 
6
+ import datetime as dt
6
7
  import logging
7
8
  import sys
8
9
  from concurrent.futures import ProcessPoolExecutor, as_completed
@@ -11,14 +12,16 @@ from typing import TYPE_CHECKING
11
12
 
12
13
  import lxml.etree as ET # noqa: N812
13
14
  import pandas as pd
15
+ import yaml
14
16
  from PySide6.QtCore import QObject, Signal
15
17
  from PySide6.QtWidgets import QDialog
16
18
 
19
+ from excel2moodle import __version__
17
20
  from excel2moodle.core import stringHelpers
18
21
  from excel2moodle.core.category import Category
19
22
  from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
20
23
  from excel2moodle.core.globals import Tags
21
- from excel2moodle.core.question import Question
24
+ from excel2moodle.core.question import ParametricQuestion, Question
22
25
  from excel2moodle.core.settings import Settings
23
26
  from excel2moodle.core.validator import Validator
24
27
  from excel2moodle.logger import LogAdapterQuestionID
@@ -72,9 +75,10 @@ class QuestionDB:
72
75
  def __init__(self, settings: Settings) -> None:
73
76
  self.settings = settings
74
77
  self.window: QMainWindow | None = None
75
- self.version = None
76
78
  self.categoriesMetaData: pd.DataFrame
77
79
  self.categories: dict[str, Category]
80
+ self._exportedQuestions: list[Question] = []
81
+ self._exportedAll: bool = False
78
82
 
79
83
  @property
80
84
  def spreadsheet(self) -> Path:
@@ -94,6 +98,17 @@ class QuestionDB:
94
98
  ``categoriesMetaData`` dataframe
95
99
  Setup the categories and store them in ``self.categories = {}``
96
100
  Pass the question data to the categories.
101
+
102
+ Raises
103
+ ------
104
+ ValueError
105
+ When there is no 'seetings' worksheet in the file.
106
+ InvalidFieldException
107
+ When the settings are invalid
108
+ Or When the categories Sheet doesn't provide the necessary keys.
109
+
110
+ Before raising it logges the exceptions with a meaningful message.
111
+
97
112
  """
98
113
  sheetPath = sheetPath if sheetPath else self.spreadsheet
99
114
  logger.info("Start Parsing the Excel Metadata Sheet\n")
@@ -104,19 +119,14 @@ class QuestionDB:
104
119
  index_col=0,
105
120
  engine="calamine",
106
121
  )
107
- logger.debug("Found the settings: \n\t%s", settingDf)
108
- settingDf = self.harmonizeDFIndex(settingDf)
109
- for tag, value in settingDf.iterrows():
110
- val = value.iloc[0]
111
- if pd.notna(val):
112
- self.settings.set(tag, val)
113
- try:
114
- self._validateProjectSettings(sheetPath=sheetPath)
115
- except InvalidFieldException:
116
- logger.exception(
117
- "Can not create the database with invalid project settings."
118
- )
119
- raise
122
+ logger.debug("Found the settings: \n\t%s", settingDf)
123
+ settingDf = self.harmonizeDFIndex(settingDf)
124
+ for tag, value in settingDf.iterrows():
125
+ val = value.iloc[0]
126
+ if pd.notna(val):
127
+ self.settings.set(tag, val)
128
+
129
+ self._validateProjectSettings(sheetPath=sheetPath)
120
130
  with Path(sheetPath).open("rb") as f:
121
131
  self.categoriesMetaData = pd.read_excel(
122
132
  f,
@@ -124,10 +134,20 @@ class QuestionDB:
124
134
  index_col=0,
125
135
  engine="calamine",
126
136
  )
127
- logger.info("Sucessfully read categoriesMetaData")
137
+ if "description" not in self.categoriesMetaData.columns:
138
+ msg = f"You need to specify the 'description' tag for each category in the sheet '{self.settings.get(Tags.CATEGORIESSHEET)}'."
139
+ raise InvalidFieldException(msg, "0000", "description")
140
+ logger.info("Sucessfully read categoriesMetaData")
128
141
  return self.categoriesMetaData
129
142
 
130
143
  def _validateProjectSettings(self, sheetPath: Path) -> None:
144
+ if Tags.LOGLEVEL in self.settings:
145
+ level: str = self.settings.get(Tags.LOGLEVEL)
146
+ if level.upper() not in ("DEBUG", "INFO", "WARNING", "ERROR"):
147
+ self.settings.pop(Tags.LOGLEVEL)
148
+ logger.warning("You specified an unsupported Loglevel: %s", level)
149
+ if self.window is not None:
150
+ self.window.logHandler.setLevel(self.settings.get(Tags.LOGLEVEL).upper())
131
151
  if Tags.IMPORTMODULE in self.settings:
132
152
  logger.warning(
133
153
  "Appending: %s to sys.path. All names defined by it will be usable",
@@ -276,7 +296,7 @@ class QuestionDB:
276
296
  for qNum in category.dataframe.columns:
277
297
  try:
278
298
  self.setupAndParseQuestion(category, qNum)
279
- except (InvalidFieldException, QNotParsedException):
299
+ except (InvalidFieldException, QNotParsedException, AttributeError):
280
300
  logger.exception(
281
301
  "Question %s%02d couldn't be parsed. The Question Data: \n %s",
282
302
  category.id,
@@ -342,21 +362,26 @@ class QuestionDB:
342
362
  else:
343
363
  msg = "couldn't setup Parser"
344
364
  raise QNotParsedException(msg, question.id)
345
- category.questions[qNumber] = question
365
+ category.appendQuestion(qNumber, question)
346
366
  return question
347
367
 
348
368
  def appendQuestions(
349
- self, questions: list[QuestionItem], file: Path | None = None
369
+ self,
370
+ questions: list[QuestionItem],
371
+ file: Path | None = None,
372
+ pCount: int = 0,
373
+ qCount: int = 0,
350
374
  ) -> None:
351
375
  """Append selected question Elements to the tree."""
376
+ self._exportedQuestions.clear()
352
377
  tree = ET.Element("quiz")
353
378
  catdict: dict[Category, list[Question]] = {}
354
379
  for q in questions:
355
380
  logger.debug(f"got a question to append {q=}")
356
- cat = q.parent().getCategory()
381
+ cat = q.parent().category
357
382
  if cat not in catdict:
358
383
  catdict[cat] = []
359
- catdict[cat].append(q.getQuestion())
384
+ catdict[cat].append(q.question)
360
385
  for cat, qlist in catdict.items():
361
386
  self._appendQElements(
362
387
  cat,
@@ -365,6 +390,8 @@ class QuestionDB:
365
390
  includeHeader=self.settings.get(Tags.INCLUDEINCATS),
366
391
  )
367
392
  stringHelpers.printDom(tree, file=file)
393
+ if self.settings.get(Tags.GENEXPORTREPORT):
394
+ self.generateExportReport(file, pCount=pCount, qCount=qCount)
368
395
 
369
396
  def _appendQElements(
370
397
  self,
@@ -373,17 +400,80 @@ class QuestionDB:
373
400
  tree: ET.Element,
374
401
  includeHeader: bool = True,
375
402
  ) -> None:
376
- if includeHeader:
403
+ variant: int = self.settings.get(Tags.QUESTIONVARIANT)
404
+ if includeHeader or variant == -1:
377
405
  tree.append(cat.getCategoryHeader())
378
406
  logger.debug(f"Appended a new category item {cat=}")
379
- variant: int = self.settings.get(Tags.QUESTIONVARIANT)
407
+ self._exportedAll: bool = True
380
408
  for q in qList:
381
- if hasattr(q, "variants") and q.variants is not None:
382
- if variant == 0 or variant > q.variants:
383
- dialog = QuestionVariantDialog(self.window, q)
384
- if dialog.exec() == QDialog.Accepted:
385
- variant = dialog.variant
386
- logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
387
- else:
388
- logger.warning("Keine Fragenvariante wurde gewählt.")
389
- tree.append(q.getUpdatedElement(variant=variant))
409
+ if not isinstance(q, ParametricQuestion):
410
+ tree.append(q.getUpdatedElement())
411
+ self._exportedQuestions.append(q)
412
+ continue
413
+ if variant == -1:
414
+ tree.append(cat.getCategoryHeader(subCategory=q.id))
415
+ for var in range(q.parametrics.variants):
416
+ tree.append(q.getUpdatedElement(variant=var))
417
+ elif variant == 0 or variant > q.parametrics.variants:
418
+ dialog = QuestionVariantDialog(self.window, q)
419
+ if dialog.exec() == QDialog.Accepted:
420
+ variant = dialog.variant
421
+ logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
422
+ else:
423
+ logger.warning("Keine Fragenvariante wurde gewählt.")
424
+ tree.append(q.getUpdatedElement(variant=variant))
425
+ else:
426
+ tree.append(q.getUpdatedElement(variant=variant))
427
+ self._exportedQuestions.append(q)
428
+
429
+ def generateExportReport(
430
+ self, file: Path | None = None, pCount: int = 0, qCount: int = 0
431
+ ) -> None:
432
+ """Generate a YAML report of the exported questions."""
433
+ if not self._exportedQuestions:
434
+ return
435
+ if file:
436
+ base_path = file.with_name(f"{file.stem}_export_report.yaml")
437
+ else:
438
+ base_path = self.spreadsheet.parent / "export_report.yaml"
439
+
440
+ for i in range(99):
441
+ report_path = base_path.with_name(f"{base_path.stem}-{i:02d}.yaml")
442
+ if not report_path.resolve().exists():
443
+ break
444
+
445
+ report_data = {
446
+ "export_metadata": {
447
+ "export_time": dt.datetime.now(tz=None).strftime("%Y-%m-%d %H:%M:%S"),
448
+ "excel2moodle_version": __version__,
449
+ },
450
+ "categories": {},
451
+ }
452
+ if qCount != 0:
453
+ report_data["export_metadata"]["question_count"] = qCount
454
+ if pCount != 0:
455
+ report_data["export_metadata"]["total_point_count"] = pCount
456
+
457
+ sorted_questions = sorted(
458
+ self._exportedQuestions, key=lambda q: (q.category.name, q.id)
459
+ )
460
+
461
+ for question in sorted_questions:
462
+ category_name = question.category.name
463
+ if category_name not in report_data["categories"]:
464
+ report_data["categories"][category_name] = {
465
+ "description": question.category.desc,
466
+ "questions": [],
467
+ }
468
+
469
+ question_data = {"id": question.id, "name": question.name}
470
+ if isinstance(question, ParametricQuestion) and question.currentVariant > 0:
471
+ if self._exportedAll:
472
+ question_data["exported_variant"] = "all"
473
+ else:
474
+ question_data["exported_variant"] = question.currentVariant + 1
475
+
476
+ report_data["categories"][category_name]["questions"].append(question_data)
477
+
478
+ with report_path.open("w") as f:
479
+ yaml.dump(report_data, f, sort_keys=False)