Changeset aaaa4b8 in flexograder
- Timestamp:
- 11/19/25 11:56:24 (5 months ago)
- Branches:
- fake-data, main, master
- Children:
- 0792ddd
- Parents:
- 7cc3672
- Files:
-
- 16 edited
-
builder/catalog_manager.py (modified) (1 diff)
-
builder/exam.py (modified) (2 diffs)
-
builder/exam_elements.py (modified) (9 diffs)
-
builder/media_items.py (modified) (7 diffs)
-
builder/question_catalog.py (modified) (1 diff)
-
builder/question_factory.py (modified) (2 diffs)
-
gui/attach_media_dialog.py (modified) (3 diffs)
-
gui/gui.py (modified) (2 diffs)
-
gui/option_question_editor.py (modified) (4 diffs)
-
gui/select_panel.py (modified) (1 diff)
-
tests/conftest.py (modified) (2 diffs)
-
tests/test_catalog_manager.py (modified) (2 diffs)
-
tests/test_exam.py (modified) (4 diffs)
-
tests/test_question_catalog.py (modified) (1 diff)
-
tests/test_question_factory.py (modified) (1 diff)
-
tests/test_questions.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
builder/catalog_manager.py
r7cc3672 raaaa4b8 149 149 # CRUD operations 150 150 # ─────────────────────────────────────────────────────────────── 151 def create(self, title: str, author: str = "unknown") -> QuestionCatalog:151 def create(self, domain_id: str, title: str, author: str = "unknown") -> QuestionCatalog: 152 152 """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 ) 157 157 self._catalogs[str(catalog.flexo_id)] = catalog 158 158 self._active_id = str(catalog.flexo_id) -
builder/exam.py
r7cc3672 raaaa4b8 82 82 def default(cls): 83 83 return cls() 84 84 85 def add_question(self, question: ExamElement): 85 86 self.questions.append(question) … … 170 171 questions = [] 171 172 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 190 193 def from_json_file(cls, filename: str): 191 194 with open(filename, "r", encoding="utf-8") as f: -
builder/exam_elements.py
r7cc3672 raaaa4b8 5 5 from dataclasses import dataclass, field 6 6 from flexoentity import FlexoEntity, EntityType, EntityState, FlexOID, logger 7 from .media_items import MediaItem, NullMediaItem, DownloadItem7 from builder.media_items import MediaItem, NullMediaItem, DownloadItem, media_factory 8 8 9 9 … … 47 47 text: str = "" 48 48 topic: str = "" 49 media: List[MediaItem] = field(default_factory=lambda: [ NullMediaItem()])49 media: List[MediaItem] = field(default_factory=lambda: []) 50 50 51 51 @classmethod 52 52 def default(cls): 53 return cls ()53 return cls.with_domain_id(domain_id="DEF_DEFAULT") 54 54 55 55 @property … … 98 98 def media_to_html(self, prefix="media/") -> str: 99 99 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 100 124 101 125 def to_dict(self): … … 105 129 "qtype": self.qtype, # avoid name clash with built-in type 106 130 "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], 108 132 "state": self.state.name, # explicit readable form 109 133 }) … … 111 135 112 136 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) 114 138 115 139 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] 117 141 118 142 … … 154 178 return d 155 179 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 156 186 157 187 @dataclass … … 187 217 }) 188 218 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 189 230 190 231 def points_for(self, option_id): … … 280 321 281 322 @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): 286 324 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", [])] 288 328 return obj 289 329 … … 363 403 return d 364 404 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 378 411 379 412 @dataclass -
builder/media_items.py
r7cc3672 raaaa4b8 23 23 # ────────────────────────────────────────────────────────────── 24 24 @property 25 def type(self) -> str:25 def mtype(self) -> str: 26 26 """Infer type from src path using MIME detection.""" 27 27 mime, _ = mimetypes.guess_type(self.src) … … 49 49 content_hash = "MISSING" 50 50 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}" 52 52 53 53 def to_dict(self): … … 57 57 "title": self.title, 58 58 "caption": self.caption, 59 "type": self. type,59 "type": self.mtype, 60 60 }) 61 61 return base … … 129 129 def default(cls): 130 130 """Return a canonical default NullMediaItem instance.""" 131 return cls ()131 return cls.with_domain_id(domain_id="NULL_MEDIA") 132 132 133 133 def __post_init__(self): … … 145 145 146 146 # FIXME: We should really check if all attributes are initialized correctly 147 147 148 148 def media_factory(m: dict) -> MediaItem: 149 149 mtype = m.get("type", "").lower() … … 151 151 domain = m.get("domain", "GEN") 152 152 label = m.get("label", None) 153 153 154 154 cls = { 155 155 "image": ImageItem, … … 159 159 }.get(mtype, NullMediaItem) 160 160 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() 163 162 return m -
builder/question_catalog.py
r7cc3672 raaaa4b8 132 132 def from_dict(cls, data: dict) -> "QuestionCatalog": 133 133 obj = cls( 134 flexo_id=data.get("flexo_id"), 134 135 title=data.get("title", ""), 135 136 author=data.get("author", "unknown"), -
builder/question_factory.py
r7cc3672 raaaa4b8 1 from typing import List,Dict, Any1 from typing import Dict, Any 2 2 3 3 from .exam_elements import ( … … 7 7 CandidateIDQuestion, 8 8 InstructionBlock, 9 Validation,10 9 AnswerOption, 10 Validation 11 11 ) 12 from .media_items import media_factory, NullMediaItem 13 from flexoentity import EntityType, EntityState, FlexOID, Domain 12 13 from builder.media_items import media_factory 14 15 # ────────────────────────────────────────────────────────────────────────────── 16 # Helper for NEW questions (no flexo_id) 17 # ────────────────────────────────────────────────────────────────────────────── 18 19 def 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 14 22 15 23 16 24 # ────────────────────────────────────────────────────────────────────────────── 17 # Helpers25 # Unified question factory 18 26 # ────────────────────────────────────────────────────────────────────────────── 19 27 20 def _ensure_flexoid_and_state(q: dict, default_domain: str) -> tuple[FlexOID, EntityState, dict]:28 def question_factory(q: Dict[str, Any]): 21 29 """ 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() 25 33 """ 26 if q.get("flexo_id"):27 oid = FlexOID(q["flexo_id"])28 p = oid.parsed()29 if not p["state"]:30 state = EntityState.DRAFT31 else:32 state = EntityState(p["state"])33 return oid, state, p34 34 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'") 40 38 39 mapping = { 40 "single_choice": SingleChoiceQuestion, 41 "multiple_choice": MultipleChoiceQuestion, 42 "text": TextQuestion, 43 "instruction": InstructionBlock, 44 "candidate_id": CandidateIDQuestion, 45 } 41 46 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}") 44 50 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) 48 54 55 # Case 2 – CREATE NEW 56 domain_id = detect_domain(q) 49 57 50 # ────────────────────────────────────────────────────────────────────────────── 51 # Question builders 52 # ────────────────────────────────────────────────────────────────────────────── 58 # domain-specific optional args 59 kwargs = { 60 "text": q.get("text", ""), 61 "topic": q.get("topic", "") 62 } 53 63 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 ] 66 70 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"]) 78 74 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", [])] 91 78 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", []) 103 82 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 15 15 self.listbox.pack(padx=10, pady=5, fill="both") 16 16 17 print(question.media) 17 18 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}") 19 20 20 21 btn_frame = ttk.Frame(self) … … 26 27 27 28 def add_media(self): 29 print("add") 28 30 path = filedialog.askopenfilename( 29 31 title="Select media file", … … 34 36 media_item = MediaItem(src=path) 35 37 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}") 37 39 38 40 def remove_media(self): -
gui/gui.py
r7cc3672 raaaa4b8 204 204 def close_catalog(self): 205 205 if not self.catalog_manager.catalogs: 206 messagebox.showerror("Catalog not found", "No catalog lef t")206 messagebox.showerror("Catalog not found", "No catalog lef") 207 207 return 208 208 self.catalog_manager.delete(self.active_catalog.flexo_id) … … 288 288 qtype = self.current_qtype_var.get() 289 289 290 media = [ NullMediaItem()]290 media = [] 291 291 292 292 if qtype == "single_choice": -
gui/option_question_editor.py
r7cc3672 raaaa4b8 34 34 # Domain 35 35 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") 37 38 self.cmb_domain.pack(fill="x", pady=(0, 10)) 38 39 … … 53 54 54 55 ttk.Button(frm, text="Attach Media...", command=self.open_media_dialog).pack(pady=5) 55 56 56 57 57 # Bottom buttons … … 76 76 77 77 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) 79 81 80 82 def on_ok(self): … … 85 87 return 86 88 89 q = self.question.with_domain 87 90 self.question.text = text 88 91 self.question.domain_code = self.cmb_domain.get() -
gui/select_panel.py
r7cc3672 raaaa4b8 72 72 parent.catalog_dropdown.bind("<<ComboboxSelected>>", parent.on_catalog_selected) 73 73 parent.catalog_dropdown.grid(row=0, column=1, sticky="ew", pady=(0, 5)) 74 -
tests/conftest.py
r7cc3672 raaaa4b8 17 17 def sample_exam(): 18 18 """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 ) 28 28 29 29 # ────────────────────────────────────────────────────────────── … … 31 31 # ────────────────────────────────────────────────────────────── 32 32 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 ) 38 38 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 ) 43 43 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 ) 52 52 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 ) 62 62 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 ) 68 68 69 69 # ────────────────────────────────────────────────────────────── -
tests/test_catalog_manager.py
r7cc3672 raaaa4b8 22 22 def sample_catalog(manager): 23 23 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") 26 27 cat.add_questions([q]) 27 28 manager.save(cat) … … 30 31 31 32 def 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") 33 34 assert str(cat.flexo_id) in manager._catalogs 34 35 assert manager.get_active_title() == "Signals" -
tests/test_exam.py
r7cc3672 raaaa4b8 11 11 exam = sample_exam 12 12 13 assert exam.domain_ code == "GENERAL"13 assert exam.domain_id == "TEST_EXAM" 14 14 assert exam.entity_type == EntityType.CATALOG 15 15 assert exam.state == EntityState.DRAFT … … 19 19 def test_to_dict_and_from_dict_roundtrip(self, sample_exam): 20 20 """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 ) 30 30 31 31 pages_before = len(sample_exam.layout.pages) … … 36 36 data = sample_exam.to_dict() 37 37 json_data = json.dumps(data) 38 print("DATA:", json_data) 38 39 restored = Exam.from_dict(json.loads(json_data)) 40 print("Restored", restored) 39 41 40 42 assert restored.title == sample_exam.title … … 76 78 def test_add_page_and_question(self, sample_exam): 77 79 """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 ) 87 89 88 90 pages_before = len(sample_exam.layout.pages) -
tests/test_question_catalog.py
r7cc3672 raaaa4b8 8 8 def sample_question(): 9 9 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) 11 12 12 13 13 14 @pytest.fixture 14 15 def 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") 19 18 cat.add_questions([sample_question]) 20 19 return cat -
tests/test_question_factory.py
r7cc3672 raaaa4b8 21 21 assert isinstance(obj, MultipleChoiceQuestion) 22 22 parsed = obj.flexo_id.parsed() 23 assert parsed["domain "] == "AF"23 assert parsed["domain_id"] == "AF" 24 24 assert parsed["state"] == "A" 25 25 -
tests/test_questions.py
r7cc3672 raaaa4b8 18 18 def test_answer_options_display_instruction(self): 19 19 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 ) 24 24 assert instr.answer_options_for_display() == [("— Instruction —", "n/a")] 25 25 26 26 def test_answer_options_display_radio(self): 27 27 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 ) 32 32 assert q.answer_options_for_display() == [("Yes", 1), ("No", 0)]
Note:
See TracChangeset
for help on using the changeset viewer.
