Changeset aaaa4b8 in flexograder


Ignore:
Timestamp:
11/19/25 11:56:24 (5 months ago)
Author:
Enrico Schwass <ennoausberlin@…>
Branches:
fake-data, main, master
Children:
0792ddd
Parents:
7cc3672
Message:

flexograder entities updated to reflect changes on FlexoEntity

Files:
16 edited

Legend:

Unmodified
Added
Removed
  • builder/catalog_manager.py

    r7cc3672 raaaa4b8  
    149149    # CRUD operations
    150150    # ───────────────────────────────────────────────────────────────
    151     def create(self, title: str, author: str = "unknown") -> QuestionCatalog:
     151    def create(self, domain_id: str, title: str, author: str = "unknown") -> QuestionCatalog:
    152152        """Create and register a new empty catalog."""
    153         catalog = QuestionCatalog(
    154             title=title,
    155             author=author,
    156         )
     153        catalog = QuestionCatalog.with_domain_id(domain_id=domain_id,
     154                                                 title=title,
     155                                                 author=author,
     156                                                 )
    157157        self._catalogs[str(catalog.flexo_id)] = catalog
    158158        self._active_id = str(catalog.flexo_id)
  • builder/exam.py

    r7cc3672 raaaa4b8  
    8282    def default(cls):
    8383        return cls()
     84
    8485    def add_question(self, question: ExamElement):
    8586        self.questions.append(question)
     
    170171            questions = []
    171172            layout = ExamLayout()
    172         # FIXME: Check if there is another state than draft   
    173         return cls(
    174             title=meta.get("title", ""),
    175             duration=meta.get("duration", ""),
    176             allowed_aids=meta.get("allowed_aids", ""),
    177             headline=meta.get("headline", ""),
    178             intro_note=meta.get("intro_note", ""),
    179             submit_note=meta.get("submit_note", ""),
    180             author=meta.get("author", "unknown"),
    181             questions=questions,
    182             meta_extra={k: v for k, v in meta.items() if k not in {
    183                 "title", "duration", "allowed_aids", "headline",
    184                 "intro_note", "submit_note", "author", "domain"
    185             }},
    186             layout=layout,
    187         )
    188 
    189     @classmethod   
     173
     174        print("Q:", questions)
     175
     176        return cls.with_domain_id(domain_id="TEST",
     177                                  title=meta.get("title", ""),
     178                                  duration=meta.get("duration", ""),
     179                                  allowed_aids=meta.get("allowed_aids", ""),
     180                                  headline=meta.get("headline", ""),
     181                                  intro_note=meta.get("intro_note", ""),
     182                                  submit_note=meta.get("submit_note", ""),
     183                                  author=meta.get("author", "unknown"),
     184                                  questions=questions,
     185                                  meta_extra={k: v for k, v in meta.items() if k not in {
     186                                      "title", "duration", "allowed_aids", "headline",
     187                                      "intro_note", "submit_note", "author", "domain"
     188                                  }},
     189                                  layout=layout,
     190                                  )
     191
     192    @classmethod
    190193    def from_json_file(cls, filename: str):
    191194        with open(filename, "r", encoding="utf-8") as f:
  • builder/exam_elements.py

    r7cc3672 raaaa4b8  
    55from dataclasses import dataclass, field
    66from flexoentity import FlexoEntity, EntityType, EntityState, FlexOID, logger
    7 from .media_items import MediaItem, NullMediaItem, DownloadItem
     7from builder.media_items import MediaItem, NullMediaItem, DownloadItem, media_factory
    88
    99
     
    4747    text: str = ""
    4848    topic: str = ""
    49     media: List[MediaItem] = field(default_factory=lambda: [NullMediaItem()])
     49    media: List[MediaItem] = field(default_factory=lambda: [])
    5050
    5151    @classmethod
    5252    def default(cls):
    53         return cls()
     53        return cls.with_domain_id(domain_id="DEF_DEFAULT")
    5454
    5555    @property
     
    9898    def media_to_html(self, prefix="media/") -> str:
    9999        return "".join([m.to_html(prefix=prefix) for m in self.media if m])
     100
     101    @classmethod
     102    def from_dict(cls, data):
     103        if "flexo_id" not in data:
     104            raise ValueError("Question is missing flexo_id")
     105
     106        flexo_id = FlexOID(data["flexo_id"])
     107
     108        # NOTE: cls(...) will call the correct subclass constructor
     109        obj = cls(
     110            text=data.get("text", ""),
     111            topic=data.get("topic", ""),
     112            flexo_id=flexo_id,
     113        )
     114
     115        # restore provenance
     116        obj.fingerprint = data.get("fingerprint", "")
     117        obj.origin = data.get("origin")
     118        if data.get("originator_id"):
     119            obj.originator_id = UUID(data["originator_id"])
     120        if data.get("owner_id"):
     121            obj.owner_id = UUID(data["owner_id"])
     122
     123        return obj
    100124
    101125    def to_dict(self):
     
    105129            "qtype": self.qtype,  # avoid name clash with built-in type
    106130            "text": self.text,
    107             "media": [m.to_dict() for m in self.media if not isinstance(m, NullMediaItem)],
     131            "media": [m.to_dict() for m in self.media],
    108132            "state": self.state.name,  # explicit readable form
    109133        })
     
    111135
    112136    def has_media(self):
    113         return any(not isinstance(item, NullMediaItem) for item in self.media)
     137        return any(item for item in self.media)
    114138
    115139    def get_media(self):
    116         return [item for item in self.media if not isinstance(item, NullMediaItem)]
     140        return [item for item in self.media]
    117141
    118142
     
    154178        return d
    155179
     180    @classmethod
     181    def from_dict(cls, data):
     182        obj = super().from_dict(data)
     183        obj.media = [media_factory(m) for m in data.get("media", [])]
     184        return obj
     185
    156186
    157187@dataclass
     
    187217        })
    188218        return base
     219
     220    @classmethod
     221    def from_dict(cls, data):
     222        obj = super().from_dict(data)
     223        obj.options = [
     224            AnswerOption(o["id"], o["text"], o.get("points", 0.0))
     225            for o in data.get("options", [])
     226        ]
     227
     228        obj.media = [media_factory(m) for m in data.get("media", [])]
     229        return obj
    189230
    190231    def points_for(self, option_id):
     
    280321
    281322    @classmethod
    282     def from_dict(cls, data: dict) -> "TextQuestion":
    283         validation = None
    284         if "validation" in data and isinstance(data["validation"], dict):
    285             validation = Validation(**data["validation"])
     323    def from_dict(cls, data):
    286324        obj = super().from_dict(data)
    287         obj.validation = validation
     325        v = data.get("validation")
     326        obj.validation = Validation(**v) if v else None
     327        obj.media = [media_factory(m) for m in data.get("media", [])]
    288328        return obj
    289329
     
    363403        return d
    364404
    365     @staticmethod
    366     def from_dict(data: dict) -> "CandidateIDQuestion":
    367         q = CandidateIDQuestion(text=data["text"],
    368                                 topic=data.get("topic", ""),
    369                                 fields=data.get("fields", []),
    370         )
    371         flexo_id = data.get("id",
    372                        FlexOID.safe_generate(domain="SYS",
    373                                              entity_type=EntityType.ITEM,
    374                                              entity_state=EntityState.DRAFT,
    375                                              ))
    376         q.flexo_id = flexo_id
    377         return q
     405    @classmethod
     406    def from_dict(cls, data):
     407        obj = super().from_dict(data)
     408        obj.fields = data.get("fields", [])
     409        obj.media = [media_factory(m) for m in data.get("media", [])]
     410        return obj
    378411
    379412@dataclass
  • builder/media_items.py

    r7cc3672 raaaa4b8  
    2323    # ──────────────────────────────────────────────────────────────
    2424    @property
    25     def type(self) -> str:
     25    def mtype(self) -> str:
    2626        """Infer type from src path using MIME detection."""
    2727        mime, _ = mimetypes.guess_type(self.src)
     
    4949            content_hash = "MISSING"
    5050
    51         return f"{self.src}|{content_hash}|{self.title}|{self.caption}|{self.type}"
     51        return f"{self.src}|{content_hash}|{self.title}|{self.caption}|{self.mtype}"
    5252
    5353    def to_dict(self):
     
    5757            "title": self.title,
    5858            "caption": self.caption,
    59             "type": self.type,
     59            "type": self.mtype,
    6060        })
    6161        return base
     
    129129    def default(cls):
    130130        """Return a canonical default NullMediaItem instance."""
    131         return cls()
     131        return cls.with_domain_id(domain_id="NULL_MEDIA")
    132132
    133133    def __post_init__(self):
     
    145145
    146146# FIXME: We should really check if all attributes are initialized correctly
    147    
     147
    148148def media_factory(m: dict) -> MediaItem:
    149149    mtype = m.get("type", "").lower()
     
    151151    domain = m.get("domain", "GEN")
    152152    label = m.get("label", None)
    153    
     153
    154154    cls = {
    155155        "image": ImageItem,
     
    159159    }.get(mtype, NullMediaItem)
    160160
    161     m = cls.with_domain(domain) if cls is not NullMediaItem else NullMediaItem.default()
    162     m.src = src
     161    m = cls.with_domain_id(domain, src=src) if cls is not NullMediaItem else NullMediaItem.default()
    163162    return m
  • builder/question_catalog.py

    r7cc3672 raaaa4b8  
    132132    def from_dict(cls, data: dict) -> "QuestionCatalog":
    133133        obj = cls(
     134            flexo_id=data.get("flexo_id"),
    134135            title=data.get("title", ""),
    135136            author=data.get("author", "unknown"),
  • builder/question_factory.py

    r7cc3672 raaaa4b8  
    1 from typing import List, Dict, Any
     1from typing import Dict, Any
    22
    33from .exam_elements import (
     
    77    CandidateIDQuestion,
    88    InstructionBlock,
    9     Validation,
    109    AnswerOption,
     10    Validation
    1111)
    12 from .media_items import media_factory, NullMediaItem
    13 from flexoentity import EntityType, EntityState, FlexOID, Domain
     12
     13from builder.media_items import media_factory
     14
     15# ──────────────────────────────────────────────────────────────────────────────
     16# Helper for NEW questions (no flexo_id)
     17# ──────────────────────────────────────────────────────────────────────────────
     18
     19def detect_domain(q: dict, fallback_domain: str = "GEN_GENERIC") -> str:
     20    """Determine domain for NEW questions (flexo_id absent)."""
     21    return q.get("domain") or fallback_domain
    1422
    1523
    1624# ──────────────────────────────────────────────────────────────────────────────
    17 # Helpers
     25# Unified question factory
    1826# ──────────────────────────────────────────────────────────────────────────────
    1927
    20 def _ensure_flexoid_and_state(q: dict, default_domain: str) -> tuple[FlexOID, EntityState, dict]:
     28def question_factory(q: Dict[str, Any]):
    2129    """
    22     I return (flexoid, state, parsed) for the question dict.
    23     - If 'flexo_id' present: parse it.
    24     - Else: generate a fresh one with DRAFT state using domain/text.
     30    Unified question loader:
     31      • If flexo_id present → restore using from_dict()
     32      • If missing → create NEW question via with_domain_id()
    2533    """
    26     if q.get("flexo_id"):
    27         oid = FlexOID(q["flexo_id"])
    28         p = oid.parsed()
    29         if not p["state"]:
    30             state = EntityState.DRAFT
    31         else:
    32             state = EntityState(p["state"])
    33         return oid, state, p
    3434
    35     domain = q.get("domain") or default_domain
    36     text = q.get("text", "")
    37     estate = EntityState.DRAFT
    38     oid = FlexOID.generate(domain, "I", estate.value, text, version=1)
    39     return oid, EntityState.DRAFT, oid.parsed()
     35    qtype = q.get("qtype")
     36    if not qtype:
     37        raise ValueError("Question missing 'qtype'")
    4038
     39    mapping = {
     40        "single_choice": SingleChoiceQuestion,
     41        "multiple_choice": MultipleChoiceQuestion,
     42        "text": TextQuestion,
     43        "instruction": InstructionBlock,
     44        "candidate_id": CandidateIDQuestion,
     45    }
    4146
    42 def options_factory(opt_list: List[Dict[str, Any]]) -> List[AnswerOption]:
    43     return [AnswerOption(o["id"], o["text"], o.get("points", 0.0)) for o in opt_list]
     47    cls = mapping.get(qtype)
     48    if not cls:
     49        raise ValueError(f"Unknown question type: {qtype}")
    4450
    45 def _media_list(q: dict):
    46     media = [media_factory(m) for m in q.get("media", [])]
    47     return media or [NullMediaItem.default()]
     51    # Case 1 – RESTORE
     52    if "flexo_id" in q:
     53        return cls.from_dict(q)
    4854
     55    # Case 2 – CREATE NEW
     56    domain_id = detect_domain(q)
    4957
    50 # ──────────────────────────────────────────────────────────────────────────────
    51 # Question builders
    52 # ──────────────────────────────────────────────────────────────────────────────
     58    # domain-specific optional args
     59    kwargs = {
     60        "text": q.get("text", ""),
     61        "topic": q.get("topic", "")
     62    }
    5363
    54 class QuestionTypes:
    55     @staticmethod
    56     def single_choice(q: dict):
    57         oid, state, p = _ensure_flexoid_and_state(q, default_domain=Domain.default().domain_code)
    58         q = SingleChoiceQuestion(
    59             text=q.get("text", ""),
    60             topic=q.get("topic", ""),
    61             options=options_factory(q.get("options", [])),
    62             media=_media_list(q),
    63         )
    64         q.flexo_id = oid
    65         return q
     64    # options
     65    if "options" in q:
     66        kwargs["options"] = [
     67            AnswerOption(o["id"], o["text"], o.get("points", 0.0))
     68            for o in q.get("options", [])
     69        ]
    6670
    67     @staticmethod
    68     def multiple_choice(q: dict):
    69         oid, state, p = _ensure_flexoid_and_state(q, default_domain=Domain.default().domain_code)
    70         q = MultipleChoiceQuestion(
    71             text=q.get("text", ""),
    72             topic=q.get("topic", ""),
    73             options=options_factory(q.get("options", [])),
    74             media=_media_list(q),
    75         )
    76         q.flexo_id = oid
    77         return q
     71    # validation
     72    if "validation" in q:
     73        kwargs["validation"] = Validation(**q["validation"])
    7874
    79     @staticmethod
    80     def text(q: dict):
    81         oid, state, p = _ensure_flexoid_and_state(q, default_domain=Domain.default().domain_code)
    82         val = Validation(**q["validation"]) if "validation" in q else None
    83         q = TextQuestion(
    84                 text=q.get("text", ""),
    85                 topic=q.get("topic", ""),
    86                 validation=val,
    87                 media=_media_list(q),
    88         )
    89         q.flexo_id = oid
    90         return q
     75    # media
     76    if "media" in q:
     77        kwargs["media"] = [media_factory(m) for m in q.get("media", [])]
    9178
    92     @staticmethod
    93     def candidate_id(q: dict):
    94         oid, state, p = _ensure_flexoid_and_state(q, default_domain="IDENT")
    95         q = CandidateIDQuestion(
    96                 text=q.get("text", ""),
    97                 topic=q.get("topic", ""),
    98                 fields=q.get("fields", []),
    99                 media=_media_list(q),
    100             )
    101         q.flexo_id = oid
    102         return q
     79    # fields (candidate ID)
     80    if "fields" in q:
     81        kwargs["fields"] = q.get("fields", [])
    10382
    104     @staticmethod
    105     def instruction(q: dict):
    106         oid, state, p = _ensure_flexoid_and_state(q, default_domain="INFO")
    107         q = InstructionBlock(
    108                 text=q.get("text", ""),
    109                 topic=q.get("topic", ""),
    110                 media=_media_list(q),
    111             )
    112         q.flexo_id = oid
    113         return q
    114 
    115 # ──────────────────────────────────────────────────────────────────────────────
    116 # Public factory
    117 # ──────────────────────────────────────────────────────────────────────────────
    118 
    119 def question_factory(q: dict):
    120     qtype = q["qtype"]
    121     try:
    122         return getattr(QuestionTypes, qtype)(q)
    123     except AttributeError as e:
    124         raise ValueError(f"Unknown question type: {qtype}") from e
     83    return cls.with_domain_id(domain_id=domain_id, **kwargs)
  • gui/attach_media_dialog.py

    r7cc3672 raaaa4b8  
    1515        self.listbox.pack(padx=10, pady=5, fill="both")
    1616
     17        print(question.media)
    1718        for m in question.media:
    18             self.listbox.insert(tk.END, f"{m.mtype.upper()}: {m.filename}")
     19            self.listbox.insert(tk.END, f"{m.mtype.upper()}: {m.src}")
    1920
    2021        btn_frame = ttk.Frame(self)
     
    2627
    2728    def add_media(self):
     29        print("add")
    2830        path = filedialog.askopenfilename(
    2931            title="Select media file",
     
    3436        media_item = MediaItem(src=path)
    3537        self.question.media.append(media_item)
    36         self.listbox.insert(tk.END, f"{media_item.type.upper()}: {path}")
     38        self.listbox.insert(tk.END, f"{media_item.mtype.upper()}: {path}")
    3739
    3840    def remove_media(self):
  • gui/gui.py

    r7cc3672 raaaa4b8  
    204204    def close_catalog(self):
    205205        if not self.catalog_manager.catalogs:
    206             messagebox.showerror("Catalog not found", "No catalog left")   
     206            messagebox.showerror("Catalog not found", "No catalog lef")
    207207            return
    208208        self.catalog_manager.delete(self.active_catalog.flexo_id)
     
    288288        qtype = self.current_qtype_var.get()
    289289
    290         media = [NullMediaItem()]
     290        media = []
    291291
    292292        if qtype == "single_choice":
  • gui/option_question_editor.py

    r7cc3672 raaaa4b8  
    3434        # Domain
    3535        ttk.Label(frm, text="Domain").pack(anchor="w")
    36         self.cmb_domain = ttk.Combobox(frm, textvariable=self.domain_var, values=self.available_domains, state="readonly")
     36        self.cmb_domain = ttk.Combobox(frm, textvariable=self.domain_var,
     37                                       values=self.available_domains, state="readonly")
    3738        self.cmb_domain.pack(fill="x", pady=(0, 10))
    3839
     
    5354
    5455        ttk.Button(frm, text="Attach Media...", command=self.open_media_dialog).pack(pady=5)
    55 
    5656
    5757        # Bottom buttons
     
    7676
    7777    def open_media_dialog(self):
    78         AttachMediaDialog(self, self.question)
     78        dlg = AttachMediaDialog(self, self.question)
     79        dlg.grab_set()
     80        self.wait_window(dlg)
    7981
    8082    def on_ok(self):
     
    8587            return
    8688
     89        q = self.question.with_domain
    8790        self.question.text = text
    8891        self.question.domain_code = self.cmb_domain.get()
  • gui/select_panel.py

    r7cc3672 raaaa4b8  
    7272        parent.catalog_dropdown.bind("<<ComboboxSelected>>", parent.on_catalog_selected)
    7373        parent.catalog_dropdown.grid(row=0, column=1, sticky="ew", pady=(0, 5))
    74 
  • tests/conftest.py

    r7cc3672 raaaa4b8  
    1717def sample_exam():
    1818    """Reusable Exam fixture with 3 pages and 5 questions (entity-consistent)."""
    19     exam = Exam(
    20         title="Signals Exam",
    21         duration="30 min",
    22         allowed_aids="none",
    23         headline="Basics of Signals",
    24         intro_note="Welcome to the exam",
    25         submit_note="Submit carefully",
    26         author="Tester",
    27     )
     19    exam = Exam.with_domain_id(domain_id="TEST_EXAM",
     20                               title="Signals Exam",
     21                               duration="30 min",
     22                               allowed_aids="none",
     23                               headline="Basics of Signals",
     24                               intro_note="Welcome to the exam",
     25                               submit_note="Submit carefully",
     26                               author="Tester",
     27                               )
    2828
    2929    # ──────────────────────────────────────────────────────────────
     
    3131    # ──────────────────────────────────────────────────────────────
    3232
    33     q_id = CandidateIDQuestion(
    34         text="Please enter your candidate ID and name.",
    35         fields=["Name", "Candidate ID"],
    36         media=[NullMediaItem.default()],
    37     )
     33    q_id = CandidateIDQuestion.with_domain_id(domain_id="ADMIN_CANDIDATE",
     34                                              text="Please enter your candidate ID and name.",
     35                                              fields=["Name", "Candidate ID"],
     36                                              media=[NullMediaItem.default()],
     37                                              )
    3838
    39     q_instr = InstructionBlock(
    40         text="Read each question carefully before answering.",
    41         media=[NullMediaItem.default()],
    42     )
     39    q_instr = InstructionBlock.with_domain_id(domain_id="EXAM_INSTRUCTION",
     40                                              text="Read each question carefully before answering.",
     41                                              media=[NullMediaItem.default()],
     42                                              )
    4343
    44     q_radio = SingleChoiceQuestion(
    45         text="What does Ohm’s law describe?",
    46         options=[
    47             AnswerOption("A", "Voltage = Current × Resistance", 1),
    48             AnswerOption("B", "Current = Voltage × Resistance", 0),
    49         ],
    50         media=[NullMediaItem.default()],
    51     )
     44    q_radio = SingleChoiceQuestion.with_domain_id(domain_id="TEST",
     45                                                  text="What does Ohm’s law describe?",
     46                                                  options=[
     47                                                      AnswerOption("A", "Voltage = Current × Resistance", 1),
     48                                                      AnswerOption("B", "Current = Voltage × Resistance", 0),
     49                                                  ],
     50                                                  media=[NullMediaItem.default()],
     51                                                  )
    5252
    53     q_multiple_choice = MultipleChoiceQuestion(
    54         text="Select all valid units of electrical power.",
    55         options=[
    56             AnswerOption("A", "Watt", 1),
    57             AnswerOption("B", "Volt", 0),
    58             AnswerOption("C", "Joule per second", 1),
    59         ],
    60         media=[NullMediaItem.default()],
    61     )
     53    q_multiple_choice = MultipleChoiceQuestion.with_domain_id(domain_id="QUESTION_EXAM",
     54                                                              text="Select all valid units of electrical power.",
     55                                                              options=[
     56                                                                  AnswerOption("A", "Watt", 1),
     57                                                                  AnswerOption("B", "Volt", 0),
     58                                                                  AnswerOption("C", "Joule per second", 1),
     59                                                              ],
     60                                                              media=[NullMediaItem.default()],
     61                                                              )
    6262
    63     q_text = TextQuestion(
    64         text="Explain briefly how resistance affects current flow.",
    65         validation=Validation(maxlength=50),
    66         media=[NullMediaItem.default()],
    67     )
     63    q_text = TextQuestion.with_domain_id(domain_id="QUESTION_TEXT",
     64                                         text="Explain briefly how resistance affects current flow.",
     65                                         validation=Validation(maxlength=50),
     66                                         media=[NullMediaItem.default()],
     67                                         )
    6868
    6969    # ──────────────────────────────────────────────────────────────
  • tests/test_catalog_manager.py

    r7cc3672 raaaa4b8  
    2222def sample_catalog(manager):
    2323    opts = [AnswerOption("A", "True", 1), AnswerOption("B", "False", 0)]
    24     q = SingleChoiceQuestion(text="Ohm’s law statement", options=opts)
    25     cat = manager.create(title="Fundamentals", author="Tester")
     24    q = SingleChoiceQuestion.with_domain_id(domain_id="TEST_CATALOG",
     25                                            text="Ohm’s law statement", options=opts)
     26    cat = manager.create(domain_id="TEST_CATALOG", title="Fundamentals", author="Tester")
    2627    cat.add_questions([q])
    2728    manager.save(cat)
     
    3031
    3132def test_create_and_list(manager):
    32     cat = manager.create(title="Signals", author="Tester")
     33    cat = manager.create(domain_id="TEST_CATALOG", title="Signals", author="Tester")
    3334    assert str(cat.flexo_id) in manager._catalogs
    3435    assert manager.get_active_title() == "Signals"
  • tests/test_exam.py

    r7cc3672 raaaa4b8  
    1111        exam = sample_exam
    1212
    13         assert exam.domain_code == "GENERAL"
     13        assert exam.domain_id == "TEST_EXAM"
    1414        assert exam.entity_type == EntityType.CATALOG
    1515        assert exam.state == EntityState.DRAFT
     
    1919    def test_to_dict_and_from_dict_roundtrip(self, sample_exam):
    2020        """Adding a new question and page must survive serialization."""
    21         q = SingleChoiceQuestion(
    22             text="What is Ohm’s law?",
    23             topic="Basics",
    24             options=[
    25                 AnswerOption("A", "U = R * I", 1),
    26                 AnswerOption("B", "I = R / U", 0),
    27             ],
    28             media=[],
    29         )
     21        q = SingleChoiceQuestion.with_domain_id(domain_id="QUESTION",
     22                                                text="What is Ohm’s law?",
     23                                                topic="Basics",
     24                                                options=[
     25                                                    AnswerOption("A", "U = R * I", 1),
     26                                                    AnswerOption("B", "I = R / U", 0),
     27                                                ],
     28                                                media=[],
     29                                                )
    3030
    3131        pages_before = len(sample_exam.layout.pages)
     
    3636        data = sample_exam.to_dict()
    3737        json_data = json.dumps(data)
     38        print("DATA:", json_data)
    3839        restored = Exam.from_dict(json.loads(json_data))
     40        print("Restored", restored)
    3941
    4042        assert restored.title == sample_exam.title
     
    7678    def test_add_page_and_question(self, sample_exam):
    7779        """Adding a page and question must update layout and content."""
    78         q = SingleChoiceQuestion(
    79             text="Which quantity is voltage?",
    80             topic="Basics",
    81             options=[
    82                 AnswerOption("A", "U", 1),
    83                 AnswerOption("B", "I", 0),
    84             ],
    85             media=[],
    86         )
     80        q = SingleChoiceQuestion.with_domain_id(domain_id="BASICS",
     81                                                text="Which quantity is voltage?",
     82                                                topic="Basics",
     83                                                options=[
     84                                                    AnswerOption("A", "U", 1),
     85                                                    AnswerOption("B", "I", 0),
     86                                                ],
     87                                                media=[],
     88                                                )
    8789
    8890        pages_before = len(sample_exam.layout.pages)
  • tests/test_question_catalog.py

    r7cc3672 raaaa4b8  
    88def sample_question():
    99    opts = [AnswerOption("A", "Yes", 1), AnswerOption("B", "No", 0)]
    10     return SingleChoiceQuestion(text="Is Ohm’s law linear?", options=opts)
     10    return SingleChoiceQuestion.with_domain_id(domain_id="GEN_GENERIC",
     11                                               text="Is Ohm’s law linear?", options=opts)
    1112
    1213
    1314@pytest.fixture
    1415def catalog(sample_question):
    15     cat = QuestionCatalog(
    16         title="Electrical Fundamentals",
    17         author="Enno"
    18     )
     16    cat = QuestionCatalog.with_domain_id(domain_id="TEST_CATALOG",
     17                                         title="Electrical Fundamentals", author="Enno")
    1918    cat.add_questions([sample_question])
    2019    return cat
  • tests/test_question_factory.py

    r7cc3672 raaaa4b8  
    2121    assert isinstance(obj, MultipleChoiceQuestion)
    2222    parsed = obj.flexo_id.parsed()
    23     assert parsed["domain"] == "AF"
     23    assert parsed["domain_id"] == "AF"
    2424    assert parsed["state"] == "A"
    2525
  • tests/test_questions.py

    r7cc3672 raaaa4b8  
    1818    def test_answer_options_display_instruction(self):
    1919
    20         instr = InstructionBlock(
    21             text="I1",
    22             topic="Read carefully.",
    23         )
     20        instr = InstructionBlock.with_domain_id(domain_id="EXAM_INSTRUCTIONS",
     21                                                text="I1",
     22                                                topic="Read carefully.",
     23                                                )
    2424        assert instr.answer_options_for_display() == [("— Instruction —", "n/a")]
    2525
    2626    def test_answer_options_display_radio(self):
    2727        opts = [AnswerOption("A", "Yes", 1), AnswerOption("B", "No", 0)]
    28         q = SingleChoiceQuestion(
    29             text="Proceed?",
    30             options=opts,
    31         )
     28        q = SingleChoiceQuestion.with_domain_id(domain_id="PY_ARITHM",
     29                                                text="Proceed?",
     30                                                options=opts,
     31                                                )
    3232        assert q.answer_options_for_display() == [("Yes", 1), ("No", 0)]
Note: See TracChangeset for help on using the changeset viewer.