Changeset e70dd79 in flexograder
- Timestamp:
- 11/25/25 19:01:53 (4 months ago)
- Branches:
- fake-data, main, master
- Children:
- 9db70f7
- Parents:
- 21f0ba8
- Files:
-
- 1 added
- 1 deleted
- 8 edited
-
builder/exam_elements.py (modified) (1 diff)
-
builder/question_catalog.py (modified) (4 diffs)
-
examples/KILE_EXAM.json (modified) (21 diffs)
-
examples/python/beginner.json (modified) (1 diff)
-
examples/python/new.json (deleted)
-
examples/python/new2.json (added)
-
gui/domain_editor_dialog.py (modified) (2 diffs)
-
gui/domain_management_dialog.py (modified) (10 diffs)
-
gui/gui.py (modified) (3 diffs)
-
gui/menu.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
builder/exam_elements.py
r21f0ba8 re70dd79 405 405 return obj 406 406 407 @dataclass408 class Signature(FlexoEntity):409 """410 I represent a digital or procedural signature for another entity.411 412 This is a stub version: I only carry logical metadata, not cryptographic proof.413 Later, platform-specific implementations (e.g. Windows certificate signing)414 can fill the 'signature_data' field with real data.415 416 Lifecycle:417 - Created in DRAFT → becomes APPROVED once verified418 - Optionally moves to PUBLISHED if distributed externally419 """420 421 ENTITY_TYPE = EntityType.OUTPUT # or define a dedicated SIGNATURE type later422 423 signed_entity: Optional[FlexOID] = None424 signer_id: Optional[UUID] = None425 signature_data: str = ""426 certificate_ref: Optional[str] = None427 comment: Optional[str] = None428 429 # Required by base class430 @property431 def text_seed(self) -> str:432 """Deterministic content used for ID and fingerprint generation."""433 return f"{self.signed_entity or ''}:{self.signer_id or ''}:{self.signature_data}"434 435 @classmethod436 def default(cls):437 """Create an empty draft signature."""438 return cls()439 440 # Optional helper: mark as verified441 def approve(self, comment: str = ""):442 """Mark this signature as verified or trusted."""443 self.comment = comment or self.comment444 super().approve()445 return self -
builder/question_catalog.py
r21f0ba8 re70dd79 4 4 from .exam_elements import ExamElement 5 5 from builder.question_factory import question_factory 6 from flexoentity import FlexoEntity, EntityType, EntityState, FlexOID 6 from flexoentity import FlexoEntity, EntityType, EntityState, FlexOID, Domain 7 7 8 8 … … 14 14 title: str = "" 15 15 author: str = "unknown" 16 domains: Dict[str, Dict[str, Any]] = field(default_factory=dict) 16 17 questions: List[ExamElement] = field(default_factory=list) 17 18 … … 34 35 def __post_init__(self): 35 36 super().__post_init__() 36 37 @property38 def domains(self) -> set[str]:39 """Return a set of all unique domains present in questions."""40 return {q.domain for q in self.questions if hasattr(q, "domain")}41 37 42 38 @property … … 140 136 141 137 # Restore questions 138 139 domains = data.get("domains", {}) 140 obj.domains = [Domain.from_dict(v)for k, v in domains.items()] 141 142 142 obj.questions = [question_factory(qd) for qd in data.get("questions", [])] 143 143 -
examples/KILE_EXAM.json
r21f0ba8 re70dd79 17 17 "GENERAL": { 18 18 "flexo_id": "GENERAL-D253110-E0C3C8A5A8A4@001D", 19 "domain_id": "GENERAL",20 19 "fullname": "General Knowledge", 21 20 "description": "Domain for general-purpose questions not tied to a specific specialty.", … … 24 23 "CS_ALGO": { 25 24 "flexo_id": "CS_ALGO-D253110-BF6AFD1A0CBD@001D", 26 "domain_id": "CS_ALGO",27 25 "fullname": "Computer Science – Algorithms", 28 26 "description": "Covers algorithmic thinking, complexity and computational procedures.", … … 31 29 "AI_BASICS": { 32 30 "flexo_id": "AI_BASICS-D253110-1748EBF9E2EF@001D", 33 "domain_id": "AI_BASICS",34 31 "fullname": "Artificial Intelligence – Basics", 35 32 "description": "Introductory concepts of artificial intelligence.", … … 38 35 "AI_ML": { 39 36 "flexo_id": "AI_ML-D253110-7E6E4A52B1D8@001D", 40 "domain_id": "AI_ML",41 37 "fullname": "Machine Learning", 42 38 "description": "Covers supervised, unsupervised and reinforcement learning methods.", … … 45 41 "AI_NN": { 46 42 "flexo_id": "AI_NN-D253110-E1F07C1B8D9A@001D", 47 "domain_id": "AI_NN",48 43 "fullname": "Neural Networks", 49 44 "description": "Structure, training and interpretation of neural networks.", … … 52 47 "AI_NLP": { 53 48 "flexo_id": "AI_NLP-D253110-3FA94F2557C6@001D", 54 "domain_id": "AI_NLP",55 49 "fullname": "Natural Language Processing", 56 50 "description": "Natural language understanding, processing and generation.", … … 59 53 "AI_TEXT": { 60 54 "flexo_id": "AI_TEXT-D253110-C16D4E72F06A@001D", 61 "domain_id": "AI_TEXT",62 55 "fullname": "AI – Text Processing", 63 56 "description": "Text classification, generation, text models and related topics.", … … 66 59 "AI_DTREE": { 67 60 "flexo_id": "AI_DTREE-D253110-A3E1E21FE26A@001D", 68 "domain_id": "AI_DTREE",69 61 "fullname": "Decision Trees", 70 62 "description": "Tree-based models, including classification and regression trees.", … … 73 65 "AI_ETHICS": { 74 66 "flexo_id": "AI_ETHICS-D253110-9B4A4B2F82C8@001D", 75 "domain_id": "AI_ETHICS",76 67 "fullname": "AI Ethics", 77 68 "description": "Ethical considerations, fairness, transparency and accountability.", … … 80 71 "WEB_DEV": { 81 72 "flexo_id": "WEB_DEV-D253110-1F2B89B5837D@001D", 82 "domain_id": "WEB_DEV",83 73 "fullname": "Web Development", 84 74 "description": "General principles, languages and environments in web development.", … … 87 77 "WEB_HTML": { 88 78 "flexo_id": "WEB_HTML-D253110-4C814DD1C4A9@001D", 89 "domain_id": "WEB_HTML",90 79 "fullname": "HTML / Markup", 91 80 "description": "Hypertext Markup Language, basic structure and semantics.", … … 94 83 "WEB_CSS": { 95 84 "flexo_id": "WEB_CSS-D253110-25C4F283F5AB@001D", 96 "domain_id": "WEB_CSS",97 85 "fullname": "CSS / Styling", 98 86 "description": "Layout, styling and formatting of web content.", … … 101 89 "WEB_FRAMEWORKS": { 102 90 "flexo_id": "WEB_FRAMEWORKS-D253110-6BF01E4DC83C@001D", 103 "domain_id": "WEB_FRAMEWORKS",104 91 "fullname": "Web Frameworks", 105 92 "description": "Tools and libraries for building complex web applications.", … … 108 95 "WEB_ARCH": { 109 96 "flexo_id": "WEB_ARCH-D253110-89F23A2B9E2C@001D", 110 "domain_id": "WEB_ARCH",111 97 "fullname": "Web Architecture", 112 98 "description": "Client/server models, APIs, scalability and distributed systems.", … … 115 101 "DATA_BIG": { 116 102 "flexo_id": "DATA_BIG-D253110-7B381C4A22C1@001D", 117 "domain_id": "DATA_BIG",118 103 "fullname": "Big Data", 119 104 "description": "Handling, analyzing and processing very large datasets.", … … 122 107 "DATA_FORMATS": { 123 108 "flexo_id": "DATA_FORMATS-D253110-3C5527DE7A19@001D", 124 "domain_id": "DATA_FORMATS",125 109 "fullname": "Data Formats", 126 110 "description": "Structured and unstructured data formats such as JSON, XML, CSV.", … … 129 113 "DB_SQL": { 130 114 "flexo_id": "DB_SQL-D253110-E7A88B21A5F6@001D", 131 "domain_id": "DB_SQL",132 115 "fullname": "Databases / SQL", 133 116 "description": "Relational databases, SQL queries and data organization.", … … 136 119 "PROG_LANG": { 137 120 "flexo_id": "PROG_LANG-D253110-0C786A1F55DA@001D", 138 "domain_id": "PROG_LANG",139 121 "fullname": "Programming Languages", 140 122 "description": "Languages, syntax, semantics and usage contexts.", … … 143 125 "SOFT_ENG": { 144 126 "flexo_id": "SOFT_ENG-D253110-A98C1C3FD19D@001D", 145 "domain_id": "SOFT_ENG",146 127 "fullname": "Software Engineering", 147 128 "description": "Processes, architecture, patterns and engineering methods.", … … 150 131 "SOFT_METHODS": { 151 132 "flexo_id": "SOFT_METHODS-D253110-1252B2BE1339@001D", 152 "domain_id": "SOFT_METHODS",153 133 "fullname": "Software Development Methods", 154 134 "description": "Agile, XP, TDD and iterative development practices.", … … 157 137 "API": { 158 138 "flexo_id": "API-D253110-4F0AEC61A8F3@001D", 159 "domain_id": "API",160 139 "fullname": "APIs / Interfaces", 161 140 "description": "Design and use of application programming interfaces.", -
examples/python/beginner.json
r21f0ba8 re70dd79 1 1 { 2 "catalog": "Python Absolute Beginner Questions", 2 "flexo_id": "PY_BEGINNER-C251122-1A2B3C4D5E6F@001D", 3 "title": "Python Absolute Beginner Questions", 4 "author": "Python Team", 3 5 "version": "1.0", 4 "generated_at": "2025-10-20", 6 "generated_at": "2025-11-22", 7 "domains": { 8 "PY_BEGINNER": { 9 "flexo_id": "PY_BEGINNER-D251125-9EEB0FCC0CFA@001D", 10 "domain_id": "PY_BEGINNER", 11 "name": "Python Beginner Essentials", 12 "description": "Einsteigerfragen zu Python: Grundlagen, Datentypen, Dateien, Schleifen, Kontrollstrukturen." 13 } 14 }, 5 15 "questions": [ 6 16 { 7 "domain_id": "PY_ARITHM", 17 "flexo_id": "PY_BEGINNER-I251122-9C2FA6D10E93@001D", 18 "domain_id": "PY_BEGINNER", 8 19 "qtype": "single_choice", 9 20 "text": "Welcher Operator führt die Ganzzahl-Division aus (abrunden Richtung −∞)?", 10 21 "options": [ 11 22 { 23 "id": "A", 12 24 "text": "//", 13 "points": 1 ,14 "id": "A"15 },16 {25 "points": 1 26 }, 27 { 28 "id": "B", 17 29 "text": "/", 18 "points": 0 ,19 "id": "B"20 },21 {30 "points": 0 31 }, 32 { 33 "id": "C", 22 34 "text": "%", 23 "points": 0 ,24 "id": "C"25 }26 ]27 },28 {29 "domain_id": "PY_ ARITHM",35 "points": 0 36 } 37 ] 38 }, 39 { 40 "flexo_id": "PY_BEGINNER-I251122-041CBAB1D8EF@001D", 41 "domain_id": "PY_BEGINNER", 30 42 "qtype": "multiple_choice", 31 43 "text": "Welche Aussagen zu int und float sind richtig?", 32 44 "options": [ 33 45 { 46 "id": "A", 34 47 "text": "int ist in der Größe nur durch den Speicher begrenzt.", 35 "points": 1 ,36 "id": "A"37 },38 {48 "points": 1 49 }, 50 { 51 "id": "B", 39 52 "text": "0.1 + 0.2 kann wegen Binär-Floats leicht von 0.3 abweichen.", 40 "points": 1 ,41 "id": "B"42 },43 {53 "points": 1 54 }, 55 { 56 "id": "C", 44 57 "text": "int hat immer exakt 64 Bit in Python.", 45 "points": 0 ,46 "id": "C"47 },48 {58 "points": 0 59 }, 60 { 61 "id": "D", 49 62 "text": "math.isclose hilft beim Vergleichen von Floats.", 50 "points": 1 ,51 "id": "D"52 }53 ]54 },55 {56 "domain_id": "PY_ ARITHM",63 "points": 1 64 } 65 ] 66 }, 67 { 68 "flexo_id": "PY_BEGINNER-I251122-682F4D78BB02@001D", 69 "domain_id": "PY_BEGINNER", 57 70 "qtype": "single_choice", 58 71 "text": "Welches Ergebnis hat 2 ** 3 ** 2 in Python?", 59 72 "options": [ 60 73 { 74 "id": "A", 61 75 "text": "512 (rechtsassoziativ)", 62 "points": 1 ,63 "id": "A"64 },65 {76 "points": 1 77 }, 78 { 79 "id": "B", 66 80 "text": "64", 67 "points": 0 ,68 "id": "B"69 },70 {81 "points": 0 82 }, 83 { 84 "id": "C", 71 85 "text": "Fehler", 72 "points": 0 ,73 "id": "C"74 }75 ]76 },77 {78 "domain_id": "PY_ ARITHM",86 "points": 0 87 } 88 ] 89 }, 90 { 91 "flexo_id": "PY_BEGINNER-I251122-59E4F519207C@001D", 92 "domain_id": "PY_BEGINNER", 79 93 "qtype": "multiple_choice", 80 94 "text": "Welche Funktionen wandeln Zahlen oder runden sinnvoll?", 81 95 "options": [ 82 96 { 97 "id": "A", 83 98 "text": "int('42')", 84 "points": 1 ,85 "id": "A"86 },87 {99 "points": 1 100 }, 101 { 102 "id": "B", 88 103 "text": "float('3.14')", 89 "points": 1 ,90 "id": "B"91 },92 {104 "points": 1 105 }, 106 { 107 "id": "C", 93 108 "text": "round(3.5)", 94 "points": 1 ,95 "id": "C"96 },97 {109 "points": 1 110 }, 111 { 112 "id": "D", 98 113 "text": "toint(3.2)", 99 "points": 0 ,100 "id": "D"101 }102 ]103 },104 {105 "domain_id": "PY_ ARITHM",114 "points": 0 115 } 116 ] 117 }, 118 { 119 "flexo_id": "PY_BEGINNER-I251122-7DA6E91C299B@001D", 120 "domain_id": "PY_BEGINNER", 106 121 "qtype": "single_choice", 107 122 "text": "Welches Paar gibt Quotient und Rest gemeinsam zurück?", 108 123 "options": [ 109 124 { 125 "id": "A", 110 126 "text": "divmod(a, b)", 111 "points": 1 ,112 "id": "A"113 },114 {127 "points": 1 128 }, 129 { 130 "id": "B", 115 131 "text": "split(a, b)", 116 "points": 0 ,117 "id": "B"118 },119 {132 "points": 0 133 }, 134 { 135 "id": "C", 120 136 "text": "quot(a, b)", 121 "points": 0 ,122 "id": "C"123 }124 ]125 },126 {127 "domain_id": "PY_ CNTRLSTRCT",137 "points": 0 138 } 139 ] 140 }, 141 { 142 "flexo_id": "PY_BEGINNER-I251122-19A961778C0E@001D", 143 "domain_id": "PY_BEGINNER", 128 144 "qtype": "single_choice", 129 145 "text": "Welche Schleife wiederholt Code, solange eine Bedingung wahr ist?", 130 146 "options": [ 131 147 { 148 "id": "A", 132 149 "text": "while", 133 "points": 1 ,134 "id": "A"135 },136 {150 "points": 1 151 }, 152 { 153 "id": "B", 137 154 "text": "if", 138 "points": 0 ,139 "id": "B"140 },141 {155 "points": 0 156 }, 157 { 158 "id": "C", 142 159 "text": "match/case", 143 "points": 0 ,144 "id": "C"145 }146 ]147 },148 {149 "domain_id": "PY_ CNTRLSTRCT",160 "points": 0 161 } 162 ] 163 }, 164 { 165 "flexo_id": "PY_BEGINNER-I251122-9565317037EF@001D", 166 "domain_id": "PY_BEGINNER", 150 167 "qtype": "multiple_choice", 151 168 "text": "Welche Schlüsselwörter passen zu Schleifen?", 152 169 "options": [ 153 170 { 171 "id": "A", 154 172 "text": "break beendet die Schleife vorzeitig.", 155 "points": 1 ,156 "id": "A"157 },158 {173 "points": 1 174 }, 175 { 176 "id": "B", 159 177 "text": "continue springt zur nächsten Iteration.", 160 "points": 1 ,161 "id": "B"162 },163 {178 "points": 1 179 }, 180 { 181 "id": "C", 164 182 "text": "redo startet die Schleife neu.", 165 "points": 0 ,166 "id": "C"167 },168 {183 "points": 0 184 }, 185 { 186 "id": "D", 169 187 "text": "else-Teil läuft, wenn kein break passierte.", 170 "points": 1 ,171 "id": "D"172 }173 ]174 },175 {176 "domain_id": "PY_ CNTRLSTRCT",188 "points": 1 189 } 190 ] 191 }, 192 { 193 "flexo_id": "PY_BEGINNER-I251122-F16429E8C6A1@001D", 194 "domain_id": "PY_BEGINNER", 177 195 "qtype": "single_choice", 178 196 "text": "Wie iteriert man idiomatisch über Index UND Wert?", 179 197 "options": [ 180 198 { 199 "id": "A", 181 200 "text": "for i, x in enumerate(seq):", 182 "points": 1 ,183 "id": "A"184 },185 {201 "points": 1 202 }, 203 { 204 "id": "B", 186 205 "text": "for i in range(seq): x = seq[i]", 187 "points": 0 ,188 "id": "B"189 },190 {206 "points": 0 207 }, 208 { 209 "id": "C", 191 210 "text": "for (i, x) in seq.items()", 192 "points": 0 ,193 "id": "C"194 }195 ]196 },197 {198 "domain_id": "PY_ CNTRLSTRCT",211 "points": 0 212 } 213 ] 214 }, 215 { 216 "flexo_id": "PY_BEGINNER-I251122-76743FD15104@001D", 217 "domain_id": "PY_BEGINNER", 199 218 "qtype": "multiple_choice", 200 219 "text": "Welche gehören zur if-Syntax?", 201 220 "options": [ 202 221 { 222 "id": "A", 203 223 "text": "if …:", 204 "points": 1 ,205 "id": "A"206 },207 {224 "points": 1 225 }, 226 { 227 "id": "B", 208 228 "text": "elif …:", 209 "points": 1 ,210 "id": "B"211 },212 {229 "points": 1 230 }, 231 { 232 "id": "C", 213 233 "text": "elseif …:", 214 "points": 0 ,215 "id": "C"216 },217 {234 "points": 0 235 }, 236 { 237 "id": "D", 218 238 "text": "else:", 219 "points": 1 ,220 "id": "D"221 }222 ]223 },224 {225 "domain_id": "PY_ CNTRLSTRCT",239 "points": 1 240 } 241 ] 242 }, 243 { 244 "flexo_id": "PY_BEGINNER-I251122-3DFA621A61F7@001D", 245 "domain_id": "PY_BEGINNER", 226 246 "qtype": "single_choice", 227 247 "text": "Wofür steht der Walrus-Operator (:=) auf Einsteiger-Niveau?", 228 248 "options": [ 229 249 { 250 "id": "A", 230 251 "text": "Zuweisung in einem Ausdruck, z. B. while (s := input()) != ''", 231 "points": 1 ,232 "id": "A"233 },234 {252 "points": 1 253 }, 254 { 255 "id": "B", 235 256 "text": "Vergleichsoperator", 236 "points": 0 ,237 "id": "B"238 },239 {257 "points": 0 258 }, 259 { 260 "id": "C", 240 261 "text": "Importoperator", 241 "points": 0 ,242 "id": "C"243 }244 ]245 },246 {247 "domain_id": "PY_ FILES",262 "points": 0 263 } 264 ] 265 }, 266 { 267 "flexo_id": "PY_BEGINNER-I251122-660F17B704AC@001D", 268 "domain_id": "PY_BEGINNER", 248 269 "qtype": "single_choice", 249 270 "text": "Wie öffnet man eine Textdatei sicher zum Lesen in UTF-8?", 250 271 "options": [ 251 272 { 273 "id": "A", 252 274 "text": "open(path, 'r', encoding='utf-8')", 253 "points": 1 ,254 "id": "A"255 },256 {275 "points": 1 276 }, 277 { 278 "id": "B", 257 279 "text": "open(path)", 258 "points": 0 ,259 "id": "B"260 },261 {280 "points": 0 281 }, 282 { 283 "id": "C", 262 284 "text": "open('utf-8', path)", 263 "points": 0 ,264 "id": "C"265 }266 ]267 },268 {269 "domain_id": "PY_ FILES",285 "points": 0 286 } 287 ] 288 }, 289 { 290 "flexo_id": "PY_BEGINNER-I251122-1EE87A1F35B1@001D", 291 "domain_id": "PY_BEGINNER", 270 292 "qtype": "multiple_choice", 271 293 "text": "Warum with beim Datei-I/O nutzen?", 272 294 "options": [ 273 295 { 296 "id": "A", 274 297 "text": "Datei wird automatisch geschlossen.", 275 "points": 1 ,276 "id": "A"277 },278 {298 "points": 1 299 }, 300 { 301 "id": "B", 279 302 "text": "Weniger Risiko für Leaks/Fehler.", 280 "points": 1 ,281 "id": "B"282 },283 {303 "points": 1 304 }, 305 { 306 "id": "C", 284 307 "text": "Code wird automatisch schneller.", 285 "points": 0 ,286 "id": "C"287 },288 {308 "points": 0 309 }, 310 { 311 "id": "D", 289 312 "text": "Kompaktere Schreibweise.", 290 "points": 1 ,291 "id": "D"292 }293 ]294 },295 {296 "domain_id": "PY_ FILES",313 "points": 1 314 } 315 ] 316 }, 317 { 318 "flexo_id": "PY_BEGINNER-I251122-56FD1E83422E@001D", 319 "domain_id": "PY_BEGINNER", 297 320 "qtype": "single_choice", 298 321 "text": "Welcher Modus hängt Text an (ohne zu überschreiben)?", 299 322 "options": [ 300 323 { 324 "id": "A", 301 325 "text": "'a'", 302 "points": 1 ,303 "id": "A"304 },305 {326 "points": 1 327 }, 328 { 329 "id": "B", 306 330 "text": "'w'", 307 "points": 0 ,308 "id": "B"309 },310 {331 "points": 0 332 }, 333 { 334 "id": "C", 311 335 "text": "'x'", 312 "points": 0 ,313 "id": "C"314 }315 ]316 },317 {318 "domain_id": "PY_ FILES",336 "points": 0 337 } 338 ] 339 }, 340 { 341 "flexo_id": "PY_BEGINNER-I251122-9233B52C30D3@001D", 342 "domain_id": "PY_BEGINNER", 319 343 "qtype": "multiple_choice", 320 344 "text": "Welche Pfad-Utilities aus der Stdlib sind nützlich?", 321 345 "options": [ 322 346 { 347 "id": "A", 323 348 "text": "pathlib.Path", 324 "points": 1 ,325 "id": "A"326 },327 {349 "points": 1 350 }, 351 { 352 "id": "B", 328 353 "text": "os.path", 329 "points": 1 ,330 "id": "B"331 },332 {354 "points": 1 355 }, 356 { 357 "id": "C", 333 358 "text": "glob", 334 "points": 1 ,335 "id": "C"336 },337 {359 "points": 1 360 }, 361 { 362 "id": "D", 338 363 "text": "sys.path zum Schreiben von Dateien", 339 "points": 0 ,340 "id": "D"341 }342 ]343 },344 {345 "domain_id": "PY_ FILES",364 "points": 0 365 } 366 ] 367 }, 368 { 369 "flexo_id": "PY_BEGINNER-I251122-1D819850078F@001D", 370 "domain_id": "PY_BEGINNER", 346 371 "qtype": "single_choice", 347 372 "text": "Wie liest man große Textdateien speicherschonend?", 348 373 "options": [ 349 374 { 375 "id": "A", 350 376 "text": "for line in f:", 351 "points": 1 ,352 "id": "A"353 },354 {377 "points": 1 378 }, 379 { 380 "id": "B", 355 381 "text": "text = f.read()", 356 "points": 0 ,357 "id": "B"358 },359 {382 "points": 0 383 }, 384 { 385 "id": "C", 360 386 "text": "lines = f.readlines()", 361 "points": 0 ,362 "id": "C"363 }364 ]365 },366 {367 "domain_id": "PY_ TYPES",387 "points": 0 388 } 389 ] 390 }, 391 { 392 "flexo_id": "PY_BEGINNER-I251122-C6D7F5428D92@001D", 393 "domain_id": "PY_BEGINNER", 368 394 "qtype": "single_choice", 369 395 "text": "Wozu dienen Typannotationen in Python?", 370 396 "options": [ 371 397 { 398 "id": "A", 372 399 "text": "Für Tools/IDE/Typchecker; Laufzeit bleibt dynamisch.", 373 "points": 1 ,374 "id": "A"375 },376 {400 "points": 1 401 }, 402 { 403 "id": "B", 377 404 "text": "Erzwingen strikt die Typen zur Laufzeit.", 378 "points": 0 ,379 "id": "B"380 },381 {405 "points": 0 406 }, 407 { 408 "id": "C", 382 409 "text": "Ersetzen Docstrings vollständig.", 383 "points": 0 ,384 "id": "C"385 }386 ]387 },388 {389 "domain_id": "PY_ TYPES",410 "points": 0 411 } 412 ] 413 }, 414 { 415 "flexo_id": "PY_BEGINNER-I251122-21BF1EB15CC4@001D", 416 "domain_id": "PY_BEGINNER", 390 417 "qtype": "multiple_choice", 391 418 "text": "Welche sind gültige Container-Annotationen (>=3.9)?", 392 419 "options": [ 393 420 { 421 "id": "A", 394 422 "text": "list[int]", 395 "points": 1 ,396 "id": "A"397 },398 {423 "points": 1 424 }, 425 { 426 "id": "B", 399 427 "text": "dict[str, int]", 400 "points": 1 ,401 "id": "B"402 },403 {428 "points": 1 429 }, 430 { 431 "id": "C", 404 432 "text": "set[float]", 405 "points": 1 ,406 "id": "C"407 },408 {433 "points": 1 434 }, 435 { 436 "id": "D", 409 437 "text": "tuple<int>", 410 "points": 0 ,411 "id": "D"412 }413 ]414 },415 {416 "domain_id": "PY_ TYPES",438 "points": 0 439 } 440 ] 441 }, 442 { 443 "flexo_id": "PY_BEGINNER-I251122-1798CB33B11A@001D", 444 "domain_id": "PY_BEGINNER", 417 445 "qtype": "single_choice", 418 446 "text": "Wie annotiert man 'oder None' präzise?", 419 447 "options": [ 420 448 { 449 "id": "A", 421 450 "text": "T | None (Optional[T])", 422 "points": 1 ,423 "id": "A"424 },425 {451 "points": 1 452 }, 453 { 454 "id": "B", 426 455 "text": "Any", 427 "points": 0 ,428 "id": "B"429 },430 {456 "points": 0 457 }, 458 { 459 "id": "C", 431 460 "text": "object", 432 "points": 0 ,433 "id": "C"434 }435 ]436 },437 {438 "domain_id": "PY_ TYPES",461 "points": 0 462 } 463 ] 464 }, 465 { 466 "flexo_id": "PY_BEGINNER-I251122-071E3FA28B05@001D", 467 "domain_id": "PY_BEGINNER", 439 468 "qtype": "multiple_choice", 440 469 "text": "Welche Stdlib-Hilfen definieren einfache Datenobjekte?", 441 470 "options": [ 442 471 { 472 "id": "A", 443 473 "text": "dataclasses.dataclass", 444 "points": 1 ,445 "id": "A"446 },447 {474 "points": 1 475 }, 476 { 477 "id": "B", 448 478 "text": "collections.namedtuple", 449 "points": 1 ,450 "id": "B"451 },452 {479 "points": 1 480 }, 481 { 482 "id": "C", 453 483 "text": "typing.TypedDict", 454 "points": 1 ,455 "id": "C"456 },457 {484 "points": 1 485 }, 486 { 487 "id": "D", 458 488 "text": "functools.singledispatch", 459 "points": 0, 460 "id": "D" 461 } 462 ] 463 }, 464 { 465 "domain_id": "PY_TYPES", 466 "qtype": "single_choice", 467 "text": "Welche Annotation beschreibt eine Funktion 'nimmt int, gibt bool'?", 468 "options": [ 469 { 470 "text": "Callable[[int], bool]", 471 "points": 1, 472 "id": "A" 473 }, 474 { 475 "text": "function(int)->bool", 476 "points": 0, 477 "id": "B" 478 }, 479 { 480 "text": "call[int->bool]", 481 "points": 0, 482 "id": "C" 483 } 484 ] 485 }, 486 { 487 "domain_id": "PY_STREAMS", 488 "qtype": "single_choice", 489 "text": "Was liefert eine Listen-Comprehension?", 490 "options": [ 491 { 492 "text": "Eine neue Liste", 493 "points": 1, 494 "id": "A" 495 }, 496 { 497 "text": "Einen Generator", 498 "points": 0, 499 "id": "B" 500 }, 501 { 502 "text": "Ein Set", 503 "points": 0, 504 "id": "C" 505 } 506 ] 507 }, 508 { 509 "domain_id": "PY_STREAMS", 510 "qtype": "multiple_choice", 511 "text": "Welche Konstrukte sind lazy (werten Elemente erst bei Bedarf aus)?", 512 "options": [ 513 { 514 "text": "Generator-Expression: (x for x in xs)", 515 "points": 1, 516 "id": "A" 517 }, 518 { 519 "text": "map(func, xs)", 520 "points": 1, 521 "id": "B" 522 }, 523 { 524 "text": "filter(func, xs)", 525 "points": 1, 526 "id": "C" 527 }, 528 { 529 "text": "[x for x in xs]", 530 "points": 0, 531 "id": "D" 532 } 533 ] 534 }, 535 { 536 "domain_id": "PY_STREAMS", 537 "qtype": "single_choice", 538 "text": "Wofür steht yield in einer Funktion?", 539 "options": [ 540 { 541 "text": "Macht die Funktion zum Generator (liefert Werte schrittweise).", 542 "points": 1, 543 "id": "A" 544 }, 545 { 546 "text": "Beendet das Programm.", 547 "points": 0, 548 "id": "B" 549 }, 550 { 551 "text": "Importiert ein Modul.", 552 "points": 0, 553 "id": "C" 554 } 555 ] 556 }, 557 { 558 "domain_id": "PY_STREAMS", 559 "qtype": "multiple_choice", 560 "text": "Welche Tools sind nützlich für Iterables?", 561 "options": [ 562 { 563 "text": "itertools (z. B. chain, islice)", 564 "points": 1, 565 "id": "A" 566 }, 567 { 568 "text": "enumerate", 569 "points": 1, 570 "id": "B" 571 }, 572 { 573 "text": "sum zum Listen-Konkatenieren", 574 "points": 0, 575 "id": "C" 576 }, 577 { 578 "text": "re als Iterable-Ersatz", 579 "points": 0, 580 "id": "D" 581 } 582 ] 583 }, 584 { 585 "domain_id": "PY_STREAMS", 586 "qtype": "single_choice", 587 "text": "Welcher Ausdruck ist am speicherschonendsten?", 588 "options": [ 589 { 590 "text": "sum(x for x in range(1_000_000))", 591 "points": 1, 592 "id": "A" 593 }, 594 { 595 "text": "sum([x for x in range(1_000_000)])", 596 "points": 0, 597 "id": "B" 598 }, 599 { 600 "text": "list(range(1_000_000))", 601 "points": 0, 602 "id": "C" 603 } 604 ] 605 }, 606 { 607 "domain_id": "PY_COLLECTIONS", 608 "qtype": "single_choice", 609 "text": "Welche Struktur eignet sich für schnelles Anhängen am Ende?", 610 "options": [ 611 { 612 "text": "list", 613 "points": 1, 614 "id": "A" 615 }, 616 { 617 "text": "tuple", 618 "points": 0, 619 "id": "B" 620 }, 621 { 622 "text": "str", 623 "points": 0, 624 "id": "C" 625 } 626 ] 627 }, 628 { 629 "domain_id": "PY_COLLECTIONS", 630 "qtype": "multiple_choice", 631 "text": "Welche Aussagen zu set sind korrekt?", 632 "options": [ 633 { 634 "text": "Speichert eindeutige, hashbare Elemente.", 635 "points": 1, 636 "id": "A" 637 }, 638 { 639 "text": "Unterstützt Vereinigung/Schnittmenge/Differenz.", 640 "points": 1, 641 "id": "B" 642 }, 643 { 644 "text": "Erhält garantiert die Einfügereihenfolge.", 645 "points": 0, 646 "id": "C" 647 }, 648 { 649 "text": "Akzeptiert Duplikate.", 650 "points": 0, 651 "id": "D" 652 } 653 ] 654 }, 655 { 656 "domain_id": "PY_COLLECTIONS", 657 "qtype": "single_choice", 658 "text": "Wofür steht collections.Counter?", 659 "options": [ 660 { 661 "text": "Zum Zählen von Element-Häufigkeiten.", 662 "points": 1, 663 "id": "A" 664 }, 665 { 666 "text": "Zum Öffnen von Dateien.", 667 "points": 0, 668 "id": "B" 669 }, 670 { 671 "text": "Zum Runden von Zahlen.", 672 "points": 0, 673 "id": "C" 674 } 675 ] 676 }, 677 { 678 "domain_id": "PY_COLLECTIONS", 679 "qtype": "multiple_choice", 680 "text": "Welche Strukturen sind unveränderlich (immutable)?", 681 "options": [ 682 { 683 "text": "tuple", 684 "points": 1, 685 "id": "A" 686 }, 687 { 688 "text": "frozenset", 689 "points": 1, 690 "id": "B" 691 }, 692 { 693 "text": "list", 694 "points": 0, 695 "id": "C" 696 }, 697 { 698 "text": "dict", 699 "points": 0, 700 "id": "D" 701 } 702 ] 703 }, 704 { 705 "domain_id": "PY_COLLECTIONS", 706 "qtype": "single_choice", 707 "text": "Was bewirkt defaultdict(list)?", 708 "options": [ 709 { 710 "text": "Fehlende Schlüssel bekommen automatisch eine leere Liste.", 711 "points": 1, 712 "id": "A" 713 }, 714 { 715 "text": "Fehlende Schlüssel werfen KeyError.", 716 "points": 0, 717 "id": "B" 718 }, 719 { 720 "text": "Es unterscheidet sich nicht von dict.", 721 "points": 0, 722 "id": "C" 723 } 724 ] 725 }, 726 { 727 "domain_id": "PY_DATETIME", 728 "qtype": "single_choice", 729 "text": "Welche Stdlib liefert Zeitzonen (ab 3.9)?", 730 "options": [ 731 { 732 "text": "zoneinfo", 733 "points": 1, 734 "id": "A" 735 }, 736 { 737 "text": "pytz", 738 "points": 0, 739 "id": "B" 740 }, 741 { 742 "text": "tzlocal", 743 "points": 0, 744 "id": "C" 745 } 746 ] 747 }, 748 { 749 "domain_id": "PY_DATETIME", 750 "qtype": "multiple_choice", 751 "text": "Welche Aussagen sind korrekt?", 752 "options": [ 753 { 754 "text": "Naive datetime hat keine Zeitzone.", 755 "points": 1, 756 "id": "A" 757 }, 758 { 759 "text": "Aware datetime enthält tzinfo.", 760 "points": 1, 761 "id": "B" 762 }, 763 { 764 "text": "UTC ist eine gute interne Referenz.", 765 "points": 1, 766 "id": "C" 767 }, 768 { 769 "text": "time.time() ist garantiert monoton.", 770 "points": 0, 771 "id": "D" 772 } 773 ] 774 }, 775 { 776 "domain_id": "PY_DATETIME", 777 "qtype": "single_choice", 778 "text": "Wie misst man kurze Zeitdauern zuverlässig?", 779 "options": [ 780 { 781 "text": "time.perf_counter()", 782 "points": 1, 783 "id": "A" 784 }, 785 { 786 "text": "datetime.now()", 787 "points": 0, 788 "id": "B" 789 }, 790 { 791 "text": "time.sleep()", 792 "points": 0, 793 "id": "C" 794 } 795 ] 796 }, 797 { 798 "domain_id": "PY_DATETIME", 799 "qtype": "multiple_choice", 800 "text": "Welche Formate sind für Logs/Datenaustausch robust?", 801 "options": [ 802 { 803 "text": "ISO-8601 (z. B. 2025-10-20T12:00:00Z)", 804 "points": 1, 805 "id": "A" 806 }, 807 { 808 "text": "Unix-Timestamp", 809 "points": 1, 810 "id": "B" 811 }, 812 { 813 "text": "Locale-abhängige Strings", 814 "points": 0, 815 "id": "C" 816 }, 817 { 818 "text": "Freitext ohne Norm", 819 "points": 0, 820 "id": "D" 821 } 822 ] 823 }, 824 { 825 "domain_id": "PY_DATETIME", 826 "qtype": "single_choice", 827 "text": "Wie erhält man 'jetzt in UTC' als aware datetime?", 828 "options": [ 829 { 830 "text": "datetime.now(timezone.utc)", 831 "points": 1, 832 "id": "A" 833 }, 834 { 835 "text": "datetime.utcnow()", 836 "points": 0, 837 "id": "B" 838 }, 839 { 840 "text": "datetime.now()", 841 "points": 0, 842 "id": "C" 843 } 844 ] 845 }, 846 { 847 "domain_id": "PY_NAMESPACES", 848 "qtype": "single_choice", 849 "text": "Wofür steht LEGB?", 850 "options": [ 851 { 852 "text": "Local, Enclosing, Global, Builtins (Namensauflösung)", 853 "points": 1, 854 "id": "A" 855 }, 856 { 857 "text": "Library, Env, Git, Bytecode", 858 "points": 0, 859 "id": "B" 860 }, 861 { 862 "text": "List, Eval, Generator, Bytes", 863 "points": 0, 864 "id": "C" 865 } 866 ] 867 }, 868 { 869 "domain_id": "PY_NAMESPACES", 870 "qtype": "multiple_choice", 871 "text": "Welche Aussagen zu global/nonlocal sind korrekt?", 872 "options": [ 873 { 874 "text": "global bezieht sich auf das Modul-Namespace.", 875 "points": 1, 876 "id": "A" 877 }, 878 { 879 "text": "nonlocal greift auf die nächst-äußere Funktionsvariable zu.", 880 "points": 1, 881 "id": "B" 882 }, 883 { 884 "text": "nonlocal kann globale Variablen binden.", 885 "points": 0, 886 "id": "C" 887 }, 888 { 889 "text": "global wirkt nur in Klassenmethoden.", 890 "points": 0, 891 "id": "D" 892 } 893 ] 894 }, 895 { 896 "domain_id": "PY_NAMESPACES", 897 "qtype": "single_choice", 898 "text": "Wie markiert man interne Modulnamen konventionell?", 899 "options": [ 900 { 901 "text": "Mit führendem Unterstrich: _name", 902 "points": 1, 903 "id": "A" 904 }, 905 { 906 "text": "Mit Großschreibung", 907 "points": 0, 908 "id": "B" 909 }, 910 { 911 "text": "Mit @internal", 912 "points": 0, 913 "id": "C" 914 } 915 ] 916 }, 917 { 918 "domain_id": "PY_NAMESPACES", 919 "qtype": "multiple_choice", 920 "text": "Gute Praktiken gegen Namenskonflikte:", 921 "options": [ 922 { 923 "text": "Keine Wildcards: kein `from x import *`.", 924 "points": 1, 925 "id": "A" 926 }, 927 { 928 "text": "Aussagekräftige Modul-/Paketnamen.", 929 "points": 1, 930 "id": "B" 931 }, 932 { 933 "text": "Konstante Namen in UPPER_CASE.", 934 "points": 1, 935 "id": "C" 936 }, 937 { 938 "text": "Alles in eine einzige Datei packen.", 939 "points": 0, 940 "id": "D" 941 } 942 ] 943 }, 944 { 945 "domain_id": "PY_NAMESPACES", 946 "qtype": "single_choice", 947 "text": "Wozu dient __all__ in einem Modul?", 948 "options": [ 949 { 950 "text": "Steuert Exporte bei `from mod import *`.", 951 "points": 1, 952 "id": "A" 953 }, 954 { 955 "text": "Definiert die Versionsnummer.", 956 "points": 0, 957 "id": "B" 958 }, 959 { 960 "text": "Erzeugt Docstrings automatisch.", 961 "points": 0, 962 "id": "C" 963 } 964 ] 965 }, 966 { 967 "domain_id": "PY_MODULES", 968 "qtype": "single_choice", 969 "text": "Was passiert mit Top-Level-Code beim Import?", 970 "options": [ 971 { 972 "text": "Er wird genau einmal ausgeführt (Initialisierung).", 973 "points": 1, 974 "id": "A" 975 }, 976 { 977 "text": "Er wird bei jedem Attributzugriff neu ausgeführt.", 978 "points": 0, 979 "id": "B" 980 }, 981 { 982 "text": "Er wird ignoriert.", 983 "points": 0, 984 "id": "C" 985 } 986 ] 987 }, 988 { 989 "domain_id": "PY_MODULES", 990 "qtype": "multiple_choice", 991 "text": "Welche Aussagen zu Imports stimmen?", 992 "options": [ 993 { 994 "text": "Module werden in sys.modules gecacht.", 995 "points": 1, 996 "id": "A" 997 }, 998 { 999 "text": "Relative Importe funktionieren nur innerhalb von Paketen.", 1000 "points": 1, 1001 "id": "B" 1002 }, 1003 { 1004 "text": "`if __name__ == '__main__':` trennt Skript-Start vom Import.", 1005 "points": 1, 1006 "id": "C" 1007 }, 1008 { 1009 "text": "`from x import *` ist immer empfohlen.", 1010 "points": 0, 1011 "id": "D" 1012 } 1013 ] 1014 }, 1015 { 1016 "domain_id": "PY_MODULES", 1017 "qtype": "single_choice", 1018 "text": "Wie führt man ein Modul als Skript aus?", 1019 "options": [ 1020 { 1021 "text": "python -m paket.modul", 1022 "points": 1, 1023 "id": "A" 1024 }, 1025 { 1026 "text": "python paket/modul", 1027 "points": 0, 1028 "id": "B" 1029 }, 1030 { 1031 "text": "run paket.modul", 1032 "points": 0, 1033 "id": "C" 1034 } 1035 ] 1036 }, 1037 { 1038 "domain_id": "PY_MODULES", 1039 "qtype": "multiple_choice", 1040 "text": "Welche Werkzeuge sind für Paket-Ressourcen nützlich?", 1041 "options": [ 1042 { 1043 "text": "importlib.resources", 1044 "points": 1, 1045 "id": "A" 1046 }, 1047 { 1048 "text": "pkgutil", 1049 "points": 1, 1050 "id": "B" 1051 }, 1052 { 1053 "text": "random.resources", 1054 "points": 0, 1055 "id": "C" 1056 }, 1057 { 1058 "text": "sys.path zum Lesen beliebiger Dateien", 1059 "points": 0, 1060 "id": "D" 1061 } 1062 ] 1063 }, 1064 { 1065 "domain_id": "PY_MODULES", 1066 "qtype": "single_choice", 1067 "text": "Wie bindet man einen Modul-Logger korrekt?", 1068 "options": [ 1069 { 1070 "text": "logger = logging.getLogger(__name__)", 1071 "points": 1, 1072 "id": "A" 1073 }, 1074 { 1075 "text": "logger = logging.getLogger()", 1076 "points": 0, 1077 "id": "B" 1078 }, 1079 { 1080 "text": "logger = print", 1081 "points": 0, 1082 "id": "C" 1083 } 1084 ] 1085 }, 1086 { 1087 "domain_id": "PY_OOP", 1088 "qtype": "single_choice", 1089 "text": "Wofür steht @property?", 1090 "options": [ 1091 { 1092 "text": "Berechnetes Attribut wie ein Getter aufrufbar.", 1093 "points": 1, 1094 "id": "A" 1095 }, 1096 { 1097 "text": "Markiert eine Klassenmethode.", 1098 "points": 0, 1099 "id": "B" 1100 }, 1101 { 1102 "text": "Erstellt automatisch __init__.", 1103 "points": 0, 1104 "id": "C" 1105 } 1106 ] 1107 }, 1108 { 1109 "domain_id": "PY_OOP", 1110 "qtype": "multiple_choice", 1111 "text": "Welche Aussagen zu Klassen-/staticmethods sind korrekt?", 1112 "options": [ 1113 { 1114 "text": "@classmethod erhält die Klasse (cls).", 1115 "points": 1, 1116 "id": "A" 1117 }, 1118 { 1119 "text": "@staticmethod erhält weder self noch cls.", 1120 "points": 1, 1121 "id": "B" 1122 }, 1123 { 1124 "text": "@classmethod == @staticmethod", 1125 "points": 0, 1126 "id": "C" 1127 }, 1128 { 1129 "text": "Beide können über die Klasse aufgerufen werden.", 1130 "points": 1, 1131 "id": "D" 1132 } 1133 ] 1134 }, 1135 { 1136 "domain_id": "PY_OOP", 1137 "qtype": "single_choice", 1138 "text": "Wie vergleicht man zwei Instanzen auf Wertgleichheit sinnvoll?", 1139 "options": [ 1140 { 1141 "text": "__eq__ implementieren (und __hash__ bei Immutables).", 1142 "points": 1, 1143 "id": "A" 1144 }, 1145 { 1146 "text": "Nur __repr__ implementieren.", 1147 "points": 0, 1148 "id": "B" 1149 }, 1150 { 1151 "text": "Mit 'is' statt '==' vergleichen.", 1152 "points": 0, 1153 "id": "C" 1154 } 1155 ] 1156 }, 1157 { 1158 "domain_id": "PY_OOP", 1159 "qtype": "multiple_choice", 1160 "text": "Welche Methoden sind für Containerklassen nützlich?", 1161 "options": [ 1162 { 1163 "text": "__len__", 1164 "points": 1, 1165 "id": "A" 1166 }, 1167 { 1168 "text": "__iter__", 1169 "points": 1, 1170 "id": "B" 1171 }, 1172 { 1173 "text": "__getitem__", 1174 "points": 1, 1175 "id": "C" 1176 }, 1177 { 1178 "text": "__cmp__", 1179 "points": 0, 1180 "id": "D" 1181 } 1182 ] 1183 }, 1184 { 1185 "domain_id": "PY_OOP", 1186 "qtype": "single_choice", 1187 "text": "Welche Basisklasse nutzt man für abstrakte Klassen?", 1188 "options": [ 1189 { 1190 "text": "abc.ABC", 1191 "points": 1, 1192 "id": "A" 1193 }, 1194 { 1195 "text": "typing.Any", 1196 "points": 0, 1197 "id": "B" 1198 }, 1199 { 1200 "text": "objectonly", 1201 "points": 0, 1202 "id": "C" 1203 } 1204 ] 1205 }, 1206 { 1207 "domain_id": "PY_EXCEPTIONS", 1208 "qtype": "single_choice", 1209 "text": "Wie fängt man eine konkrete Exception?", 1210 "options": [ 1211 { 1212 "text": "try: ... except ValueError:", 1213 "points": 1, 1214 "id": "A" 1215 }, 1216 { 1217 "text": "try: ... catch ValueError:", 1218 "points": 0, 1219 "id": "B" 1220 }, 1221 { 1222 "text": "except(ValueError): ... ohne try", 1223 "points": 0, 1224 "id": "C" 1225 } 1226 ] 1227 }, 1228 { 1229 "domain_id": "PY_EXCEPTIONS", 1230 "qtype": "multiple_choice", 1231 "text": "Welche Blöcke kann ein try-Statement enthalten?", 1232 "options": [ 1233 { 1234 "text": "except", 1235 "points": 1, 1236 "id": "A" 1237 }, 1238 { 1239 "text": "else", 1240 "points": 1, 1241 "id": "B" 1242 }, 1243 { 1244 "text": "finally", 1245 "points": 1, 1246 "id": "C" 1247 }, 1248 { 1249 "text": "then", 1250 "points": 0, 1251 "id": "D" 1252 } 1253 ] 1254 }, 1255 { 1256 "domain_id": "PY_EXCEPTIONS", 1257 "qtype": "single_choice", 1258 "text": "Wie re-raiset man dieselbe Exception innerhalb des except-Blocks unverändert?", 1259 "options": [ 1260 { 1261 "text": "raise (ohne Argumente)", 1262 "points": 1, 1263 "id": "A" 1264 }, 1265 { 1266 "text": "return e", 1267 "points": 0, 1268 "id": "B" 1269 }, 1270 { 1271 "text": "throw e", 1272 "points": 0, 1273 "id": "C" 1274 } 1275 ] 1276 }, 1277 { 1278 "domain_id": "PY_EXCEPTIONS", 1279 "qtype": "multiple_choice", 1280 "text": "Gute Praktiken beim Fehler-Handling:", 1281 "options": [ 1282 { 1283 "text": "Spezifische Exceptions abfangen.", 1284 "points": 1, 1285 "id": "A" 1286 }, 1287 { 1288 "text": "Kontext mit `raise ... from e` erhalten.", 1289 "points": 1, 1290 "id": "B" 1291 }, 1292 { 1293 "text": "Bare `except:` nur in seltenen Fällen.", 1294 "points": 1, 1295 "id": "C" 1296 }, 1297 { 1298 "text": "Alle Fehler still ignorieren.", 1299 "points": 0, 1300 "id": "D" 1301 } 1302 ] 1303 }, 1304 { 1305 "domain_id": "PY_EXCEPTIONS", 1306 "qtype": "single_choice", 1307 "text": "Welche Ausnahmen sind KEINE Unterklassen von Exception?", 1308 "options": [ 1309 { 1310 "text": "SystemExit / KeyboardInterrupt / GeneratorExit", 1311 "points": 1, 1312 "id": "A" 1313 }, 1314 { 1315 "text": "RuntimeError", 1316 "points": 0, 1317 "id": "B" 1318 }, 1319 { 1320 "text": "ValueError", 1321 "points": 0, 1322 "id": "C" 489 "points": 0 1323 490 } 1324 491 ] -
gui/domain_editor_dialog.py
r21f0ba8 re70dd79 40 40 41 41 self.transient(parent) 42 self.grab_set() 43 parent.wait_window(self) 42 self.protocol("WM_DELETE_WINDOW", self.on_close) 44 43 45 44 def on_ok(self): … … 51 50 messagebox.showerror("Missing data", "Domain ID and Full name are required.") 52 51 return 53 54 52 self.result = {"domain_id": domain_id, "fullname": fullname, 55 53 "description": desc} 56 54 self.destroy() 55 56 def on_close(self): 57 self.result = None 58 self.destroy() -
gui/domain_management_dialog.py
r21f0ba8 re70dd79 1 1 import tkinter as tk 2 2 from tkinter import ttk, messagebox 3 from dataclasses import dataclass 3 4 from flexoentity import Domain 4 5 5 6 from .domain_editor_dialog import DomainEditDialog 6 7 7 from flexoentity import Domain8 8 9 9 class DomainManagementDialog(tk.Toplevel): … … 18 18 19 19 self.result = None 20 20 21 21 self._build_ui() 22 22 self._populate() … … 24 24 self.transient(parent) 25 25 self.grab_set() 26 parent.wait_window(self)26 self.protocol("WM_DELETE_WINDOW", self.on_close) 27 27 28 # ──────────────────────────────────────────────────────────────────────29 28 def _build_ui(self): 30 29 # Treeview … … 40 39 41 40 # Buttons 42 ttk.Button(self, text="Add", command=self.on_add).grid(row=1, column=0, padx=4, pady=6, sticky="ew") 43 ttk.Button(self, text="Edit", command=self.on_edit).grid(row=1, column=1, padx=4, pady=6, sticky="ew") 44 ttk.Button(self, text="Delete", command=self.on_delete).grid(row=1, column=2, padx=4, pady=6, sticky="ew") 45 ttk.Button(self, text="Close", command=self.on_close).grid(row=1, column=3, padx=4, pady=6, sticky="ew") 41 ttk.Button(self, text="Add", command=self.on_add).grid(row=1, column=0, 42 padx=4, pady=6, 43 sticky="ew") 44 ttk.Button(self, text="Edit", command=self.on_edit).grid(row=1, column=1, 45 padx=4, pady=6, 46 sticky="ew") 47 ttk.Button(self, text="Delete", command=self.on_delete).grid(row=1, column=2, 48 padx=4, pady=6, 49 sticky="ew") 50 ttk.Button(self, text="Close", command=self.on_close).grid(row=1, column=3, 51 padx=4, pady=6, 52 sticky="ew") 46 53 47 # ──────────────────────────────────────────────────────────────────────48 54 def _populate(self): 49 55 self.tree.delete(*self.tree.get_children()) … … 52 58 values=(dom.domain_id, dom.fullname, dom.description)) 53 59 54 # ──────────────────────────────────────────────────────────────────────55 60 def _get_selected(self): 56 61 sel = self.tree.selection() … … 58 63 return None 59 64 return self.domain_manager.get(sel[0]) 60 # ────────────────────────────────────────────────────────────────────── 65 61 66 def on_add(self): 67 self.grab_release() 62 68 dlg = DomainEditDialog(self) 69 self.wait_window(dlg) 70 self.grab_set() 63 71 if dlg.result: 64 72 if dlg.result["domain_id"] in self.domain_manager.all_domain_ids(): 65 messagebox.showerror("Duplicate", f"Domain {dlg.result["domain_id"]} already exists.") 73 messagebox.showerror("Duplicate", 74 f"Domain {dlg.result['domain_id']} already exists.") 66 75 return 67 76 68 77 try: 69 Domain.with_domain_id(domain_id=dlg.result["domain_id"], 70 fullname=dlg.result["fullname"], 71 description=dlg.result["description"]) 78 self.domain_manager.register( 79 Domain.with_domain_id(domain_id=dlg.result["domain_id"], 80 fullname=dlg.result["fullname"], 81 description=dlg.result["description"])) 72 82 except ValueError as e: 73 83 messagebox.showerror("Format error", f"The values you entered are invalid {e}" ) … … 75 85 self._populate() 76 86 77 # ──────────────────────────────────────────────────────────────────────78 87 def on_edit(self): 79 88 dom = self._get_selected() … … 81 90 messagebox.showinfo("No selection", "Select a domain first.") 82 91 return 92 self.grab_release() 83 93 dlg = DomainEditDialog(self, dom, editing=True) 94 self.wait_window(dlg) 95 self.grab_set() 84 96 if dlg.result: 85 97 dom.fullname = dlg.result["fullname"] … … 87 99 self._populate() 88 100 89 # ──────────────────────────────────────────────────────────────────────90 101 def on_delete(self): 91 102 dom = self._get_selected() … … 104 115 # FIXME: Return a real result like current list of domains 105 116 def on_close(self): 106 self.result = "Whatever" 117 self.result = self.domain_manager 118 self.grab_release() 107 119 self.destroy() 120 -
gui/gui.py
r21f0ba8 re70dd79 199 199 200 200 catalog = QuestionCatalog.from_dict(data) 201 for each in catalog.domains: 202 self.domain_manager.register(each) 201 203 self.catalog_manager.add_catalog(catalog) 202 204 self.catalog_manager.set_active(catalog.flexo_id) … … 632 634 self.log_action(f"Pasted {len(transferred)} question(s) to '{target.title}'.") 633 635 634 def add_domain(self):636 def manage_domain(self): 635 637 active = self.require_active_catalog() 636 638 … … 639 641 640 642 dlg = DomainManagementDialog(self, self.domain_manager) 641 self.wait_window() 642 if not dlg: 643 return 644 645 try: 646 self.domain_manager.register(Domain.with_domain_id(dlg.result)) 647 self.log_action(f"Added - Domain '{dlg.result}' added to catalog '{active.catalog_id}'.") 648 except ValueError as e: 649 messagebox.showerror("Invalid Input", str(e)) 650 return 643 self.wait_window(dlg) 644 return 651 645 652 646 def on_quit(self): -
gui/menu.py
r21f0ba8 re70dd79 23 23 24 24 domain_menu = tk.Menu(self, tearoff=0) 25 domain_menu.add_command(label=" Add domain", command=parent.add_domain)25 domain_menu.add_command(label="Manage domain", command=parent.manage_domain) 26 26 self.add_cascade(label="Domains", menu=domain_menu) 27 27
Note:
See TracChangeset
for help on using the changeset viewer.
