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.
- {excel2moodle-0.6.1/excel2moodle.egg-info → excel2moodle-0.6.3}/PKG-INFO +66 -19
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/README.md +64 -18
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/__init__.py +2 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/__main__.py +18 -3
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/bullets.py +2 -2
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/category.py +27 -8
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/dataStructure.py +122 -32
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/etHelpers.py +0 -20
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/globals.py +0 -9
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/parser.py +21 -13
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/question.py +43 -23
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/settings.py +16 -5
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/extra/equationVerification.py +0 -2
- excel2moodle-0.6.3/excel2moodle/extra/updateQuery.py +48 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/extra/variableGenerator.py +73 -49
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/cloze.py +119 -87
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/mc.py +15 -10
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/nf.py +7 -1
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/nfm.py +14 -10
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_exportSettingsDialog.py +60 -14
- excel2moodle-0.6.3/excel2moodle/ui/UI_updateDlg.py +106 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/appUi.py +66 -24
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/dialogs.py +30 -1
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/treewidget.py +30 -10
- {excel2moodle-0.6.1 → excel2moodle-0.6.3/excel2moodle.egg-info}/PKG-INFO +66 -19
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/SOURCES.txt +3 -1
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/requires.txt +1 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/pyproject.toml +12 -2
- excel2moodle-0.6.3/test/test_feedbacking.py +73 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_nfmParsing.py +41 -12
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_parseQuestion.py +0 -14
- excel2moodle-0.6.1/MANIFEST.in +0 -1
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/LICENSE +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/__init__.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/exceptions.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/stringHelpers.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/core/validator.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/extra/__init__.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/logger.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/question_types/__init__.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_equationChecker.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_mainWindow.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_variableGenerator.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/UI_variantDialog.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/__init__.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle/ui/equationChecker.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/dependency_links.txt +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/entry_points.txt +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/excel2moodle.egg-info/top_level.txt +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/setup.cfg +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_bullets.py +0 -0
- {excel2moodle-0.6.1 → excel2moodle-0.6.3}/test/test_picture.py +0 -0
- {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.
|
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
|
-
{width=35%}
|
24
|
+
{width=35%}
|
24
25
|
|
25
|
-
|
26
|
-
The idea is to write the questions data into a spreadsheet file, from which the
|
27
|
-
All questions or a selection of questions can
|
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
|
-
|
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
|
-
##
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
*
|
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
|
-
{width=80%}
|
79
|
+
{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
|
-
{width=35%}
|
2
|
+
{width=35%}
|
3
3
|
|
4
|
-
|
5
|
-
The idea is to write the questions data into a spreadsheet file, from which the
|
6
|
-
All questions or a selection of questions can
|
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
|
-
|
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
|
-
##
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
*
|
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
|
-
{width=80%}
|
57
|
+
{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
|
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
|
29
|
-
|
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.
|
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
|
-
|
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 =
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
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.
|
365
|
+
category.appendQuestion(qNumber, question)
|
346
366
|
return question
|
347
367
|
|
348
368
|
def appendQuestions(
|
349
|
-
self,
|
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().
|
381
|
+
cat = q.parent().category
|
357
382
|
if cat not in catdict:
|
358
383
|
catdict[cat] = []
|
359
|
-
catdict[cat].append(q.
|
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
|
-
|
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
|
-
|
407
|
+
self._exportedAll: bool = True
|
380
408
|
for q in qList:
|
381
|
-
if
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
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)
|