Changeset 20b84df in flexograder


Ignore:
Timestamp:
11/22/25 22:44:06 (8 weeks ago)
Author:
Enrico Schwass <ennoausberlin@…>
Branches:
master
Children:
36bee44
Parents:
858a4dc
Message:

convert example catalog to new format

Signed-off-by: Enrico Schwass <ennoausberlin@…>

Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • examples/python/beginner.json

    r858a4dc r20b84df  
    55  "questions": [
    66    {
    7       "domain": "PY_ARITHM",
     7      "domain_id": "PY_ARITHM",
    88      "qtype": "single_choice",
    99      "text": "Welcher Operator führt die Ganzzahl-Division aus (abrunden Richtung −∞)?",
     
    2727    },
    2828    {
    29       "domain": "PY_ARITHM",
     29      "domain_id": "PY_ARITHM",
    3030      "qtype": "multiple_choice",
    3131      "text": "Welche Aussagen zu int und float sind richtig?",
     
    5454    },
    5555    {
    56       "domain": "PY_ARITHM",
     56      "domain_id": "PY_ARITHM",
    5757      "qtype": "single_choice",
    5858      "text": "Welches Ergebnis hat 2 ** 3 ** 2 in Python?",
     
    7676    },
    7777    {
    78       "domain": "PY_ARITHM",
     78      "domain_id": "PY_ARITHM",
    7979      "qtype": "multiple_choice",
    8080      "text": "Welche Funktionen wandeln Zahlen oder runden sinnvoll?",
     
    103103    },
    104104    {
    105       "domain": "PY_ARITHM",
     105      "domain_id": "PY_ARITHM",
    106106      "qtype": "single_choice",
    107107      "text": "Welches Paar gibt Quotient und Rest gemeinsam zurück?",
     
    125125    },
    126126    {
    127       "domain": "PY_CNTRLSTRCT",
     127      "domain_id": "PY_CNTRLSTRCT",
    128128      "qtype": "single_choice",
    129129      "text": "Welche Schleife wiederholt Code, solange eine Bedingung wahr ist?",
     
    147147    },
    148148    {
    149       "domain": "PY_CNTRLSTRCT",
     149      "domain_id": "PY_CNTRLSTRCT",
    150150      "qtype": "multiple_choice",
    151151      "text": "Welche Schlüsselwörter passen zu Schleifen?",
     
    174174    },
    175175    {
    176       "domain": "PY_CNTRLSTRCT",
     176      "domain_id": "PY_CNTRLSTRCT",
    177177      "qtype": "single_choice",
    178178      "text": "Wie iteriert man idiomatisch über Index UND Wert?",
     
    196196    },
    197197    {
    198       "domain": "PY_CNTRLSTRCT",
     198      "domain_id": "PY_CNTRLSTRCT",
    199199      "qtype": "multiple_choice",
    200200      "text": "Welche gehören zur if-Syntax?",
     
    223223    },
    224224    {
    225       "domain": "PY_CNTRLSTRCT",
     225      "domain_id": "PY_CNTRLSTRCT",
    226226      "qtype": "single_choice",
    227227      "text": "Wofür steht der Walrus-Operator (:=) auf Einsteiger-Niveau?",
     
    245245    },
    246246    {
    247       "domain": "PY_FILES",
     247      "domain_id": "PY_FILES",
    248248      "qtype": "single_choice",
    249249      "text": "Wie öffnet man eine Textdatei sicher zum Lesen in UTF-8?",
     
    267267    },
    268268    {
    269       "domain": "PY_FILES",
     269      "domain_id": "PY_FILES",
    270270      "qtype": "multiple_choice",
    271271      "text": "Warum with beim Datei-I/O nutzen?",
     
    294294    },
    295295    {
    296       "domain": "PY_FILES",
     296      "domain_id": "PY_FILES",
    297297      "qtype": "single_choice",
    298298      "text": "Welcher Modus hängt Text an (ohne zu überschreiben)?",
     
    316316    },
    317317    {
    318       "domain": "PY_FILES",
     318      "domain_id": "PY_FILES",
    319319      "qtype": "multiple_choice",
    320320      "text": "Welche Pfad-Utilities aus der Stdlib sind nützlich?",
     
    343343    },
    344344    {
    345       "domain": "PY_FILES",
     345      "domain_id": "PY_FILES",
    346346      "qtype": "single_choice",
    347347      "text": "Wie liest man große Textdateien speicherschonend?",
     
    365365    },
    366366    {
    367       "domain": "PY_TYPES",
     367      "domain_id": "PY_TYPES",
    368368      "qtype": "single_choice",
    369369      "text": "Wozu dienen Typannotationen in Python?",
     
    387387    },
    388388    {
    389       "domain": "PY_TYPES",
     389      "domain_id": "PY_TYPES",
    390390      "qtype": "multiple_choice",
    391391      "text": "Welche sind gültige Container-Annotationen (>=3.9)?",
     
    414414    },
    415415    {
    416       "domain": "PY_TYPES",
     416      "domain_id": "PY_TYPES",
    417417      "qtype": "single_choice",
    418418      "text": "Wie annotiert man 'oder None' präzise?",
     
    436436    },
    437437    {
    438       "domain": "PY_TYPES",
     438      "domain_id": "PY_TYPES",
    439439      "qtype": "multiple_choice",
    440440      "text": "Welche Stdlib-Hilfen definieren einfache Datenobjekte?",
     
    463463    },
    464464    {
    465       "domain": "PY_TYPES",
     465      "domain_id": "PY_TYPES",
    466466      "qtype": "single_choice",
    467467      "text": "Welche Annotation beschreibt eine Funktion 'nimmt int, gibt bool'?",
     
    485485    },
    486486    {
    487       "domain": "PY_STREAMS",
     487      "domain_id": "PY_STREAMS",
    488488      "qtype": "single_choice",
    489489      "text": "Was liefert eine Listen-Comprehension?",
     
    507507    },
    508508    {
    509       "domain": "PY_STREAMS",
     509      "domain_id": "PY_STREAMS",
    510510      "qtype": "multiple_choice",
    511511      "text": "Welche Konstrukte sind lazy (werten Elemente erst bei Bedarf aus)?",
     
    534534    },
    535535    {
    536       "domain": "PY_STREAMS",
     536      "domain_id": "PY_STREAMS",
    537537      "qtype": "single_choice",
    538538      "text": "Wofür steht yield in einer Funktion?",
     
    556556    },
    557557    {
    558       "domain": "PY_STREAMS",
     558      "domain_id": "PY_STREAMS",
    559559      "qtype": "multiple_choice",
    560560      "text": "Welche Tools sind nützlich für Iterables?",
     
    583583    },
    584584    {
    585       "domain": "PY_STREAMS",
     585      "domain_id": "PY_STREAMS",
    586586      "qtype": "single_choice",
    587587      "text": "Welcher Ausdruck ist am speicherschonendsten?",
     
    605605    },
    606606    {
    607       "domain": "PY_COLLECTIONS",
     607      "domain_id": "PY_COLLECTIONS",
    608608      "qtype": "single_choice",
    609609      "text": "Welche Struktur eignet sich für schnelles Anhängen am Ende?",
     
    627627    },
    628628    {
    629       "domain": "PY_COLLECTIONS",
     629      "domain_id": "PY_COLLECTIONS",
    630630      "qtype": "multiple_choice",
    631631      "text": "Welche Aussagen zu set sind korrekt?",
     
    654654    },
    655655    {
    656       "domain": "PY_COLLECTIONS",
     656      "domain_id": "PY_COLLECTIONS",
    657657      "qtype": "single_choice",
    658658      "text": "Wofür steht collections.Counter?",
     
    676676    },
    677677    {
    678       "domain": "PY_COLLECTIONS",
     678      "domain_id": "PY_COLLECTIONS",
    679679      "qtype": "multiple_choice",
    680680      "text": "Welche Strukturen sind unveränderlich (immutable)?",
     
    703703    },
    704704    {
    705       "domain": "PY_COLLECTIONS",
     705      "domain_id": "PY_COLLECTIONS",
    706706      "qtype": "single_choice",
    707707      "text": "Was bewirkt defaultdict(list)?",
     
    725725    },
    726726    {
    727       "domain": "PY_DATETIME",
     727      "domain_id": "PY_DATETIME",
    728728      "qtype": "single_choice",
    729729      "text": "Welche Stdlib liefert Zeitzonen (ab 3.9)?",
     
    747747    },
    748748    {
    749       "domain": "PY_DATETIME",
     749      "domain_id": "PY_DATETIME",
    750750      "qtype": "multiple_choice",
    751751      "text": "Welche Aussagen sind korrekt?",
     
    774774    },
    775775    {
    776       "domain": "PY_DATETIME",
     776      "domain_id": "PY_DATETIME",
    777777      "qtype": "single_choice",
    778778      "text": "Wie misst man kurze Zeitdauern zuverlässig?",
     
    796796    },
    797797    {
    798       "domain": "PY_DATETIME",
     798      "domain_id": "PY_DATETIME",
    799799      "qtype": "multiple_choice",
    800800      "text": "Welche Formate sind für Logs/Datenaustausch robust?",
     
    823823    },
    824824    {
    825       "domain": "PY_DATETIME",
     825      "domain_id": "PY_DATETIME",
    826826      "qtype": "single_choice",
    827827      "text": "Wie erhält man 'jetzt in UTC' als aware datetime?",
     
    845845    },
    846846    {
    847       "domain": "PY_NAMESPACES",
     847      "domain_id": "PY_NAMESPACES",
    848848      "qtype": "single_choice",
    849849      "text": "Wofür steht LEGB?",
     
    867867    },
    868868    {
    869       "domain": "PY_NAMESPACES",
     869      "domain_id": "PY_NAMESPACES",
    870870      "qtype": "multiple_choice",
    871871      "text": "Welche Aussagen zu global/nonlocal sind korrekt?",
     
    894894    },
    895895    {
    896       "domain": "PY_NAMESPACES",
     896      "domain_id": "PY_NAMESPACES",
    897897      "qtype": "single_choice",
    898898      "text": "Wie markiert man interne Modulnamen konventionell?",
     
    916916    },
    917917    {
    918       "domain": "PY_NAMESPACES",
     918      "domain_id": "PY_NAMESPACES",
    919919      "qtype": "multiple_choice",
    920920      "text": "Gute Praktiken gegen Namenskonflikte:",
     
    943943    },
    944944    {
    945       "domain": "PY_NAMESPACES",
     945      "domain_id": "PY_NAMESPACES",
    946946      "qtype": "single_choice",
    947947      "text": "Wozu dient __all__ in einem Modul?",
     
    965965    },
    966966    {
    967       "domain": "PY_MODULES",
     967      "domain_id": "PY_MODULES",
    968968      "qtype": "single_choice",
    969969      "text": "Was passiert mit Top-Level-Code beim Import?",
     
    987987    },
    988988    {
    989       "domain": "PY_MODULES",
     989      "domain_id": "PY_MODULES",
    990990      "qtype": "multiple_choice",
    991991      "text": "Welche Aussagen zu Imports stimmen?",
     
    10141014    },
    10151015    {
    1016       "domain": "PY_MODULES",
     1016      "domain_id": "PY_MODULES",
    10171017      "qtype": "single_choice",
    10181018      "text": "Wie führt man ein Modul als Skript aus?",
     
    10361036    },
    10371037    {
    1038       "domain": "PY_MODULES",
     1038      "domain_id": "PY_MODULES",
    10391039      "qtype": "multiple_choice",
    10401040      "text": "Welche Werkzeuge sind für Paket-Ressourcen nützlich?",
     
    10631063    },
    10641064    {
    1065       "domain": "PY_MODULES",
     1065      "domain_id": "PY_MODULES",
    10661066      "qtype": "single_choice",
    10671067      "text": "Wie bindet man einen Modul-Logger korrekt?",
     
    10851085    },
    10861086    {
    1087       "domain": "PY_OOP",
     1087      "domain_id": "PY_OOP",
    10881088      "qtype": "single_choice",
    10891089      "text": "Wofür steht @property?",
     
    11071107    },
    11081108    {
    1109       "domain": "PY_OOP",
     1109      "domain_id": "PY_OOP",
    11101110      "qtype": "multiple_choice",
    11111111      "text": "Welche Aussagen zu Klassen-/staticmethods sind korrekt?",
     
    11341134    },
    11351135    {
    1136       "domain": "PY_OOP",
     1136      "domain_id": "PY_OOP",
    11371137      "qtype": "single_choice",
    11381138      "text": "Wie vergleicht man zwei Instanzen auf Wertgleichheit sinnvoll?",
     
    11561156    },
    11571157    {
    1158       "domain": "PY_OOP",
     1158      "domain_id": "PY_OOP",
    11591159      "qtype": "multiple_choice",
    11601160      "text": "Welche Methoden sind für Containerklassen nützlich?",
     
    11831183    },
    11841184    {
    1185       "domain": "PY_OOP",
     1185      "domain_id": "PY_OOP",
    11861186      "qtype": "single_choice",
    11871187      "text": "Welche Basisklasse nutzt man für abstrakte Klassen?",
     
    12051205    },
    12061206    {
    1207       "domain": "PY_EXCEPTIONS",
     1207      "domain_id": "PY_EXCEPTIONS",
    12081208      "qtype": "single_choice",
    12091209      "text": "Wie fängt man eine konkrete Exception?",
     
    12271227    },
    12281228    {
    1229       "domain": "PY_EXCEPTIONS",
     1229      "domain_id": "PY_EXCEPTIONS",
    12301230      "qtype": "multiple_choice",
    12311231      "text": "Welche Blöcke kann ein try-Statement enthalten?",
     
    12541254    },
    12551255    {
    1256       "domain": "PY_EXCEPTIONS",
     1256      "domain_id": "PY_EXCEPTIONS",
    12571257      "qtype": "single_choice",
    12581258      "text": "Wie re-raiset man dieselbe Exception innerhalb des except-Blocks unverändert?",
     
    12761276    },
    12771277    {
    1278       "domain": "PY_EXCEPTIONS",
     1278      "domain_id": "PY_EXCEPTIONS",
    12791279      "qtype": "multiple_choice",
    12801280      "text": "Gute Praktiken beim Fehler-Handling:",
     
    13031303    },
    13041304    {
    1305       "domain": "PY_EXCEPTIONS",
     1305      "domain_id": "PY_EXCEPTIONS",
    13061306      "qtype": "single_choice",
    13071307      "text": "Welche Ausnahmen sind KEINE Unterklassen von Exception?",
  • gui/exam_layout_editor.py

    r858a4dc r20b84df  
    22import tkinter as tk
    33from tkinter import ttk, messagebox, simpledialog
     4
     5from builder.exam import ExamLayout
    46
    57
     
    1517        self.layout = getattr(exam, "layout", None)
    1618        if self.layout is None:
    17             from builder.exam import ExamLayout
     19            print("No layout")
    1820            self.layout = ExamLayout()
    1921
     
    2325
    2426        self.create_widgets()
     27        print("DEBUG pages:", self.layout.pages)
     28
    2529        self.refresh_all()
    2630
     
    5559        self.questions_list = tk.Listbox(top, selectmode="extended", exportselection=False)
    5660        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
    5772        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))
    5974        ttk.Button(qbtns, text="Add Question", command=self.add_question).pack(side="left")
    6075        ttk.Button(qbtns, text="Delete", command=self.delete_question).pack(side="left")
     
    108123    # ─────────────────────────────────────────────────────────────
    109124    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()
    119128
    120129    def refresh_page_questions(self, page):
     
    123132            q = self.exam.get_question_by_id(qid)
    124133            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])
    127156    # ─────────────────────────────────────────────────────────────
    128157    # Page operations
  • gui/gui.py

    r858a4dc r20b84df  
    386386            messagebox.showerror("Error", f"Could not load exam:\n{e}")
    387387            return
     388        self.refresh_all()
    388389
    389390    def import_exam_as_temp_catalog(self, exam_path: str):
  • gui/menu.py

    r858a4dc r20b84df  
    2828        exam_menu = tk.Menu(self, tearoff=0)
    2929        exam_menu.add_command(label="Create new exam", command=parent.create_exam_dialog)
    30         exam_menu.add_command(label="Layout existing exam", command=parent.layout_exam)
     30        exam_menu.add_command(label="Layout current exam", command=parent.layout_exam)
    3131        self.add_cascade(label="Exams", menu=exam_menu)
    3232
Note: See TracChangeset for help on using the changeset viewer.