Changeset 20b84df in flexograder
- Timestamp:
- 11/22/25 22:44:06 (8 weeks ago)
- Branches:
- master
- Children:
- 36bee44
- Parents:
- 858a4dc
- Files:
-
- 1 added
- 4 edited
-
examples/python/beginner.json (modified) (55 diffs)
-
examples/python/new.json (added)
-
gui/exam_layout_editor.py (modified) (6 diffs)
-
gui/gui.py (modified) (1 diff)
-
gui/menu.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
examples/python/beginner.json
r858a4dc r20b84df 5 5 "questions": [ 6 6 { 7 "domain ": "PY_ARITHM",7 "domain_id": "PY_ARITHM", 8 8 "qtype": "single_choice", 9 9 "text": "Welcher Operator führt die Ganzzahl-Division aus (abrunden Richtung −∞)?", … … 27 27 }, 28 28 { 29 "domain ": "PY_ARITHM",29 "domain_id": "PY_ARITHM", 30 30 "qtype": "multiple_choice", 31 31 "text": "Welche Aussagen zu int und float sind richtig?", … … 54 54 }, 55 55 { 56 "domain ": "PY_ARITHM",56 "domain_id": "PY_ARITHM", 57 57 "qtype": "single_choice", 58 58 "text": "Welches Ergebnis hat 2 ** 3 ** 2 in Python?", … … 76 76 }, 77 77 { 78 "domain ": "PY_ARITHM",78 "domain_id": "PY_ARITHM", 79 79 "qtype": "multiple_choice", 80 80 "text": "Welche Funktionen wandeln Zahlen oder runden sinnvoll?", … … 103 103 }, 104 104 { 105 "domain ": "PY_ARITHM",105 "domain_id": "PY_ARITHM", 106 106 "qtype": "single_choice", 107 107 "text": "Welches Paar gibt Quotient und Rest gemeinsam zurück?", … … 125 125 }, 126 126 { 127 "domain ": "PY_CNTRLSTRCT",127 "domain_id": "PY_CNTRLSTRCT", 128 128 "qtype": "single_choice", 129 129 "text": "Welche Schleife wiederholt Code, solange eine Bedingung wahr ist?", … … 147 147 }, 148 148 { 149 "domain ": "PY_CNTRLSTRCT",149 "domain_id": "PY_CNTRLSTRCT", 150 150 "qtype": "multiple_choice", 151 151 "text": "Welche Schlüsselwörter passen zu Schleifen?", … … 174 174 }, 175 175 { 176 "domain ": "PY_CNTRLSTRCT",176 "domain_id": "PY_CNTRLSTRCT", 177 177 "qtype": "single_choice", 178 178 "text": "Wie iteriert man idiomatisch über Index UND Wert?", … … 196 196 }, 197 197 { 198 "domain ": "PY_CNTRLSTRCT",198 "domain_id": "PY_CNTRLSTRCT", 199 199 "qtype": "multiple_choice", 200 200 "text": "Welche gehören zur if-Syntax?", … … 223 223 }, 224 224 { 225 "domain ": "PY_CNTRLSTRCT",225 "domain_id": "PY_CNTRLSTRCT", 226 226 "qtype": "single_choice", 227 227 "text": "Wofür steht der Walrus-Operator (:=) auf Einsteiger-Niveau?", … … 245 245 }, 246 246 { 247 "domain ": "PY_FILES",247 "domain_id": "PY_FILES", 248 248 "qtype": "single_choice", 249 249 "text": "Wie öffnet man eine Textdatei sicher zum Lesen in UTF-8?", … … 267 267 }, 268 268 { 269 "domain ": "PY_FILES",269 "domain_id": "PY_FILES", 270 270 "qtype": "multiple_choice", 271 271 "text": "Warum with beim Datei-I/O nutzen?", … … 294 294 }, 295 295 { 296 "domain ": "PY_FILES",296 "domain_id": "PY_FILES", 297 297 "qtype": "single_choice", 298 298 "text": "Welcher Modus hängt Text an (ohne zu überschreiben)?", … … 316 316 }, 317 317 { 318 "domain ": "PY_FILES",318 "domain_id": "PY_FILES", 319 319 "qtype": "multiple_choice", 320 320 "text": "Welche Pfad-Utilities aus der Stdlib sind nützlich?", … … 343 343 }, 344 344 { 345 "domain ": "PY_FILES",345 "domain_id": "PY_FILES", 346 346 "qtype": "single_choice", 347 347 "text": "Wie liest man große Textdateien speicherschonend?", … … 365 365 }, 366 366 { 367 "domain ": "PY_TYPES",367 "domain_id": "PY_TYPES", 368 368 "qtype": "single_choice", 369 369 "text": "Wozu dienen Typannotationen in Python?", … … 387 387 }, 388 388 { 389 "domain ": "PY_TYPES",389 "domain_id": "PY_TYPES", 390 390 "qtype": "multiple_choice", 391 391 "text": "Welche sind gültige Container-Annotationen (>=3.9)?", … … 414 414 }, 415 415 { 416 "domain ": "PY_TYPES",416 "domain_id": "PY_TYPES", 417 417 "qtype": "single_choice", 418 418 "text": "Wie annotiert man 'oder None' präzise?", … … 436 436 }, 437 437 { 438 "domain ": "PY_TYPES",438 "domain_id": "PY_TYPES", 439 439 "qtype": "multiple_choice", 440 440 "text": "Welche Stdlib-Hilfen definieren einfache Datenobjekte?", … … 463 463 }, 464 464 { 465 "domain ": "PY_TYPES",465 "domain_id": "PY_TYPES", 466 466 "qtype": "single_choice", 467 467 "text": "Welche Annotation beschreibt eine Funktion 'nimmt int, gibt bool'?", … … 485 485 }, 486 486 { 487 "domain ": "PY_STREAMS",487 "domain_id": "PY_STREAMS", 488 488 "qtype": "single_choice", 489 489 "text": "Was liefert eine Listen-Comprehension?", … … 507 507 }, 508 508 { 509 "domain ": "PY_STREAMS",509 "domain_id": "PY_STREAMS", 510 510 "qtype": "multiple_choice", 511 511 "text": "Welche Konstrukte sind lazy (werten Elemente erst bei Bedarf aus)?", … … 534 534 }, 535 535 { 536 "domain ": "PY_STREAMS",536 "domain_id": "PY_STREAMS", 537 537 "qtype": "single_choice", 538 538 "text": "Wofür steht yield in einer Funktion?", … … 556 556 }, 557 557 { 558 "domain ": "PY_STREAMS",558 "domain_id": "PY_STREAMS", 559 559 "qtype": "multiple_choice", 560 560 "text": "Welche Tools sind nützlich für Iterables?", … … 583 583 }, 584 584 { 585 "domain ": "PY_STREAMS",585 "domain_id": "PY_STREAMS", 586 586 "qtype": "single_choice", 587 587 "text": "Welcher Ausdruck ist am speicherschonendsten?", … … 605 605 }, 606 606 { 607 "domain ": "PY_COLLECTIONS",607 "domain_id": "PY_COLLECTIONS", 608 608 "qtype": "single_choice", 609 609 "text": "Welche Struktur eignet sich für schnelles Anhängen am Ende?", … … 627 627 }, 628 628 { 629 "domain ": "PY_COLLECTIONS",629 "domain_id": "PY_COLLECTIONS", 630 630 "qtype": "multiple_choice", 631 631 "text": "Welche Aussagen zu set sind korrekt?", … … 654 654 }, 655 655 { 656 "domain ": "PY_COLLECTIONS",656 "domain_id": "PY_COLLECTIONS", 657 657 "qtype": "single_choice", 658 658 "text": "Wofür steht collections.Counter?", … … 676 676 }, 677 677 { 678 "domain ": "PY_COLLECTIONS",678 "domain_id": "PY_COLLECTIONS", 679 679 "qtype": "multiple_choice", 680 680 "text": "Welche Strukturen sind unveränderlich (immutable)?", … … 703 703 }, 704 704 { 705 "domain ": "PY_COLLECTIONS",705 "domain_id": "PY_COLLECTIONS", 706 706 "qtype": "single_choice", 707 707 "text": "Was bewirkt defaultdict(list)?", … … 725 725 }, 726 726 { 727 "domain ": "PY_DATETIME",727 "domain_id": "PY_DATETIME", 728 728 "qtype": "single_choice", 729 729 "text": "Welche Stdlib liefert Zeitzonen (ab 3.9)?", … … 747 747 }, 748 748 { 749 "domain ": "PY_DATETIME",749 "domain_id": "PY_DATETIME", 750 750 "qtype": "multiple_choice", 751 751 "text": "Welche Aussagen sind korrekt?", … … 774 774 }, 775 775 { 776 "domain ": "PY_DATETIME",776 "domain_id": "PY_DATETIME", 777 777 "qtype": "single_choice", 778 778 "text": "Wie misst man kurze Zeitdauern zuverlässig?", … … 796 796 }, 797 797 { 798 "domain ": "PY_DATETIME",798 "domain_id": "PY_DATETIME", 799 799 "qtype": "multiple_choice", 800 800 "text": "Welche Formate sind für Logs/Datenaustausch robust?", … … 823 823 }, 824 824 { 825 "domain ": "PY_DATETIME",825 "domain_id": "PY_DATETIME", 826 826 "qtype": "single_choice", 827 827 "text": "Wie erhält man 'jetzt in UTC' als aware datetime?", … … 845 845 }, 846 846 { 847 "domain ": "PY_NAMESPACES",847 "domain_id": "PY_NAMESPACES", 848 848 "qtype": "single_choice", 849 849 "text": "Wofür steht LEGB?", … … 867 867 }, 868 868 { 869 "domain ": "PY_NAMESPACES",869 "domain_id": "PY_NAMESPACES", 870 870 "qtype": "multiple_choice", 871 871 "text": "Welche Aussagen zu global/nonlocal sind korrekt?", … … 894 894 }, 895 895 { 896 "domain ": "PY_NAMESPACES",896 "domain_id": "PY_NAMESPACES", 897 897 "qtype": "single_choice", 898 898 "text": "Wie markiert man interne Modulnamen konventionell?", … … 916 916 }, 917 917 { 918 "domain ": "PY_NAMESPACES",918 "domain_id": "PY_NAMESPACES", 919 919 "qtype": "multiple_choice", 920 920 "text": "Gute Praktiken gegen Namenskonflikte:", … … 943 943 }, 944 944 { 945 "domain ": "PY_NAMESPACES",945 "domain_id": "PY_NAMESPACES", 946 946 "qtype": "single_choice", 947 947 "text": "Wozu dient __all__ in einem Modul?", … … 965 965 }, 966 966 { 967 "domain ": "PY_MODULES",967 "domain_id": "PY_MODULES", 968 968 "qtype": "single_choice", 969 969 "text": "Was passiert mit Top-Level-Code beim Import?", … … 987 987 }, 988 988 { 989 "domain ": "PY_MODULES",989 "domain_id": "PY_MODULES", 990 990 "qtype": "multiple_choice", 991 991 "text": "Welche Aussagen zu Imports stimmen?", … … 1014 1014 }, 1015 1015 { 1016 "domain ": "PY_MODULES",1016 "domain_id": "PY_MODULES", 1017 1017 "qtype": "single_choice", 1018 1018 "text": "Wie führt man ein Modul als Skript aus?", … … 1036 1036 }, 1037 1037 { 1038 "domain ": "PY_MODULES",1038 "domain_id": "PY_MODULES", 1039 1039 "qtype": "multiple_choice", 1040 1040 "text": "Welche Werkzeuge sind für Paket-Ressourcen nützlich?", … … 1063 1063 }, 1064 1064 { 1065 "domain ": "PY_MODULES",1065 "domain_id": "PY_MODULES", 1066 1066 "qtype": "single_choice", 1067 1067 "text": "Wie bindet man einen Modul-Logger korrekt?", … … 1085 1085 }, 1086 1086 { 1087 "domain ": "PY_OOP",1087 "domain_id": "PY_OOP", 1088 1088 "qtype": "single_choice", 1089 1089 "text": "Wofür steht @property?", … … 1107 1107 }, 1108 1108 { 1109 "domain ": "PY_OOP",1109 "domain_id": "PY_OOP", 1110 1110 "qtype": "multiple_choice", 1111 1111 "text": "Welche Aussagen zu Klassen-/staticmethods sind korrekt?", … … 1134 1134 }, 1135 1135 { 1136 "domain ": "PY_OOP",1136 "domain_id": "PY_OOP", 1137 1137 "qtype": "single_choice", 1138 1138 "text": "Wie vergleicht man zwei Instanzen auf Wertgleichheit sinnvoll?", … … 1156 1156 }, 1157 1157 { 1158 "domain ": "PY_OOP",1158 "domain_id": "PY_OOP", 1159 1159 "qtype": "multiple_choice", 1160 1160 "text": "Welche Methoden sind für Containerklassen nützlich?", … … 1183 1183 }, 1184 1184 { 1185 "domain ": "PY_OOP",1185 "domain_id": "PY_OOP", 1186 1186 "qtype": "single_choice", 1187 1187 "text": "Welche Basisklasse nutzt man für abstrakte Klassen?", … … 1205 1205 }, 1206 1206 { 1207 "domain ": "PY_EXCEPTIONS",1207 "domain_id": "PY_EXCEPTIONS", 1208 1208 "qtype": "single_choice", 1209 1209 "text": "Wie fängt man eine konkrete Exception?", … … 1227 1227 }, 1228 1228 { 1229 "domain ": "PY_EXCEPTIONS",1229 "domain_id": "PY_EXCEPTIONS", 1230 1230 "qtype": "multiple_choice", 1231 1231 "text": "Welche Blöcke kann ein try-Statement enthalten?", … … 1254 1254 }, 1255 1255 { 1256 "domain ": "PY_EXCEPTIONS",1256 "domain_id": "PY_EXCEPTIONS", 1257 1257 "qtype": "single_choice", 1258 1258 "text": "Wie re-raiset man dieselbe Exception innerhalb des except-Blocks unverändert?", … … 1276 1276 }, 1277 1277 { 1278 "domain ": "PY_EXCEPTIONS",1278 "domain_id": "PY_EXCEPTIONS", 1279 1279 "qtype": "multiple_choice", 1280 1280 "text": "Gute Praktiken beim Fehler-Handling:", … … 1303 1303 }, 1304 1304 { 1305 "domain ": "PY_EXCEPTIONS",1305 "domain_id": "PY_EXCEPTIONS", 1306 1306 "qtype": "single_choice", 1307 1307 "text": "Welche Ausnahmen sind KEINE Unterklassen von Exception?", -
gui/exam_layout_editor.py
r858a4dc r20b84df 2 2 import tkinter as tk 3 3 from tkinter import ttk, messagebox, simpledialog 4 5 from builder.exam import ExamLayout 4 6 5 7 … … 15 17 self.layout = getattr(exam, "layout", None) 16 18 if self.layout is None: 17 from builder.exam import ExamLayout19 print("No layout") 18 20 self.layout = ExamLayout() 19 21 … … 23 25 24 26 self.create_widgets() 27 print("DEBUG pages:", self.layout.pages) 28 25 29 self.refresh_all() 26 30 … … 55 59 self.questions_list = tk.Listbox(top, selectmode="extended", exportselection=False) 56 60 self.questions_list.grid(row=1, column=0, sticky="nsew", padx=(0, 6)) 61 62 # Filter — show only unassigned questions 63 self.var_unassigned = tk.BooleanVar(value=False) 64 self.chk_unassigned = ttk.Checkbutton( 65 top, 66 text="Show unassigned only", 67 variable=self.var_unassigned, 68 command=self.refresh_questions_list 69 ) 70 self.chk_unassigned.grid(row=2, column=0, sticky="w", padx=(0, 6), pady=(4, 0)) 71 57 72 qbtns = ttk.Frame(top) 58 qbtns.grid(row= 2, column=0, sticky="ew", pady=(5, 0))73 qbtns.grid(row=3, column=0, sticky="ew", pady=(5, 0)) 59 74 ttk.Button(qbtns, text="Add Question", command=self.add_question).pack(side="left") 60 75 ttk.Button(qbtns, text="Delete", command=self.delete_question).pack(side="left") … … 108 123 # ───────────────────────────────────────────────────────────── 109 124 def refresh_all(self): 110 self.pages_list.delete(0, tk.END) 111 for p in self.layout.pages: 112 self.pages_list.insert(tk.END, p.title) 113 114 self.questions_list.delete(0, tk.END) 115 for q in self.exam.questions: 116 self.questions_list.insert(tk.END, q.text[:50]) 117 118 self.page_questions.delete(0, tk.END) 125 126 self.refresh_pages_list() 127 self.refresh_questions_list() 119 128 120 129 def refresh_page_questions(self, page): … … 123 132 q = self.exam.get_question_by_id(qid) 124 133 if q: 125 self.page_questions.insert(tk.END, f"{qid} — {q.text[:50]}") 126 134 self.page_questions.insert(tk.END, q.text[:50]) 135 136 def refresh_pages_list(self): 137 """Refresh the right-side pages list.""" 138 self.pages_list.delete(0, tk.END) 139 for p in self.layout.pages: 140 self.pages_list.insert(tk.END, str(p.title)) 141 142 def refresh_questions_list(self): 143 """Refresh left-side question pool, considering the 'unassigned only' filter.""" 144 self.questions_list.delete(0, tk.END) 145 146 # Collect all question IDs currently placed on pages 147 assigned = set() 148 for page in self.layout.pages: 149 for qid in page.question_ids: 150 assigned.add(qid) 151 152 for q in self.exam.questions: 153 if self.var_unassigned.get() and q.flexo_id in assigned: 154 continue 155 self.questions_list.insert(tk.END, q.text[:50]) 127 156 # ───────────────────────────────────────────────────────────── 128 157 # Page operations -
gui/gui.py
r858a4dc r20b84df 386 386 messagebox.showerror("Error", f"Could not load exam:\n{e}") 387 387 return 388 self.refresh_all() 388 389 389 390 def import_exam_as_temp_catalog(self, exam_path: str): -
gui/menu.py
r858a4dc r20b84df 28 28 exam_menu = tk.Menu(self, tearoff=0) 29 29 exam_menu.add_command(label="Create new exam", command=parent.create_exam_dialog) 30 exam_menu.add_command(label="Layout existingexam", command=parent.layout_exam)30 exam_menu.add_command(label="Layout current exam", command=parent.layout_exam) 31 31 self.add_cascade(label="Exams", menu=exam_menu) 32 32
Note:
See TracChangeset
for help on using the changeset viewer.
