From 098dc9c8db57d3b02c211e3490662fdcce947eab Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 22 Dec 2025 01:20:48 +0100 Subject: [PATCH] vault backup: 2025-12-22 01:20:48 --- content/.obsidian/workspace.json | 34 +- .../Gamla tentor/2023-05-31/1.md | 2 +- .../Gamla tentor/2023-05-31/2.md | 4 +- .../Gamla tentor/2023-05-31/3.md | 15 +- .../Gamla tentor/2023-05-31/4.md | 9 +- .../Gamla tentor/2023-05-31/5.md | 2 +- .../Gamla tentor/2023-05-31/7.md | 2 +- .../Gamla tentor/2023-05-31/8.md | 9 +- quiz/ADMIN_README.md | 0 quiz/db.sqlite3-wal | Bin 4132392 -> 4132392 bytes quiz/pytest.ini | 17 + quiz/quiz/__pycache__/admin.cpython-314.pyc | Bin 8860 -> 10531 bytes quiz/quiz/__pycache__/apps.cpython-314.pyc | Bin 1593 -> 1815 bytes .../__pycache__/middleware.cpython-314.pyc | Bin 1410 -> 1423 bytes quiz/quiz/__pycache__/models.cpython-314.pyc | Bin 3968 -> 6146 bytes quiz/quiz/__pycache__/tests.cpython-314.pyc | Bin 0 -> 11713 bytes quiz/quiz/__pycache__/views.cpython-314.pyc | Bin 3315 -> 3317 bytes quiz/quiz/admin.py | 51 +- quiz/quiz/apps.py | 5 +- .../populate_exams.cpython-314.pyc | Bin 0 -> 4206 bytes .../management/commands/populate_exams.py | 87 +++ quiz/quiz/middleware.py | 6 +- ...me_user_quizuser_alter_quizuser_options.py | 21 + .../0005_course_exam_question_exam.py | 44 ++ ...ser_alter_quizuser_options.cpython-314.pyc | Bin 0 -> 900 bytes ..._course_exam_question_exam.cpython-314.pyc | Bin 0 -> 2449 bytes quiz/quiz/models.py | 34 +- .../__pycache__/importer.cpython-314.pyc | Bin 16528 -> 17935 bytes quiz/quiz/utils/importer.py | 68 ++- quiz/quiz/views.py | 8 +- quiz/tests/__init__.py | 2 + .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 158 bytes .../conftest.cpython-314-pytest-9.0.2.pyc | Bin 0 -> 2773 bytes .../test_admin.cpython-314-pytest-9.0.2.pyc | Bin 0 -> 33526 bytes .../test_import.cpython-314-pytest-9.0.2.pyc | Bin 0 -> 58529 bytes quiz/tests/conftest.py | 108 ++++ quiz/tests/test_admin.py | 184 ++++++ quiz/tests/test_import.py | 576 ++++++++++++++++++ 38 files changed, 1225 insertions(+), 63 deletions(-) delete mode 100644 quiz/ADMIN_README.md create mode 100644 quiz/pytest.ini create mode 100644 quiz/quiz/__pycache__/tests.cpython-314.pyc create mode 100644 quiz/quiz/management/commands/__pycache__/populate_exams.cpython-314.pyc create mode 100644 quiz/quiz/management/commands/populate_exams.py create mode 100644 quiz/quiz/migrations/0004_rename_user_quizuser_alter_quizuser_options.py create mode 100644 quiz/quiz/migrations/0005_course_exam_question_exam.py create mode 100644 quiz/quiz/migrations/__pycache__/0004_rename_user_quizuser_alter_quizuser_options.cpython-314.pyc create mode 100644 quiz/quiz/migrations/__pycache__/0005_course_exam_question_exam.cpython-314.pyc create mode 100644 quiz/tests/__init__.py create mode 100644 quiz/tests/__pycache__/__init__.cpython-314.pyc create mode 100644 quiz/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc create mode 100644 quiz/tests/__pycache__/test_admin.cpython-314-pytest-9.0.2.pyc create mode 100644 quiz/tests/__pycache__/test_import.cpython-314-pytest-9.0.2.pyc create mode 100644 quiz/tests/conftest.py create mode 100644 quiz/tests/test_admin.py create mode 100644 quiz/tests/test_import.py diff --git a/content/.obsidian/workspace.json b/content/.obsidian/workspace.json index 3a84831..62ef886 100644 --- a/content/.obsidian/workspace.json +++ b/content/.obsidian/workspace.json @@ -13,33 +13,31 @@ "state": { "type": "markdown", "state": { - "file": "Anatomi & Histologi 2/Statistik.md", + "file": "Anatomi & Histologi 2/Gamla tentor/2023-05-31/8.md", "mode": "source", - "source": false, + "source": true, "backlinks": false }, "icon": "lucide-file", - "title": "Statistik" + "title": "8" } } ] }, { - "id": "a3f459e33e387813", + "id": "7e72057acf1e42f0", "type": "tabs", "children": [ { - "id": "088f03735e333992", + "id": "c1c7815735aa906e", "type": "leaf", - "pinned": true, "state": { "type": "pdf", "state": { - "file": "Anatomi & Histologi 2/Gamla tentor/2023-01-11/!2023-01-11-0044-PRX.pdf" + "file": "Anatomi & Histologi 2/Gamla tentor/2023-05-31/!2023-05-31-0100-DKS.pdf" }, - "pinned": true, "icon": "lucide-file-text", - "title": "!2023-01-11-0044-PRX" + "title": "!2023-05-31-0100-DKS" } } ] @@ -213,9 +211,17 @@ }, "active": "baa45c5e57825965", "lastOpenFiles": [ - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/1.md", + "Anatomi & Histologi 2/Gamla tentor/2023-05-31/7.md", + "Anatomi & Histologi 2/Gamla tentor/2023-05-31/6.md", + "Anatomi & Histologi 2/Gamla tentor/2023-05-31/5.md", + "Anatomi & Histologi 2/Gamla tentor/2023-05-31/4.md", + "Anatomi & Histologi 2/Gamla tentor/2023-05-31/3.md", + "Anatomi & Histologi 2/Gamla tentor/2023-05-31/2.md", "Anatomi & Histologi 2/Gamla tentor/2023-05-31/1.md", + "Anatomi & Histologi 2/Gamla tentor/2023-05-31/!2023-05-31-0100-DKS.pdf", + "Anatomi & Histologi 2/Schema.md", "Anatomi & Histologi 2/Statistik.md", + "Anatomi & Histologi 2/Gamla tentor/2023-01-11/1.md", "Anatomi & Histologi 2/Gamla tentor/2023-01-11/29.md", "Anatomi & Histologi 2/Gamla tentor/2023-01-11/30.md", "Anatomi & Histologi 2/Gamla tentor/2023-01-11/28.md", @@ -233,13 +239,6 @@ "Anatomi & Histologi 2/Gamla tentor/2023-01-11/17.md", "Anatomi & Histologi 2/Gamla tentor/2023-01-11/16.md", "Anatomi & Histologi 2/Gamla tentor/2023-01-11/15.md", - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/14.md", - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/13.md", - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/12.md", - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/11.md", - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/10.md", - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/9.md", - "Anatomi & Histologi 2/Gamla tentor/2023-01-11/8.md", "Anatomi & Histologi 2/Gamla tentor/2022-06-01/!2022-06-01-0101-MGY.pdf", "Anatomi & Histologi 2/Gamla tentor/2022-01-15/!2022-01-15-0032-BWD.pdf", "attachments/image-121.png", @@ -258,7 +257,6 @@ "attachments/image-112.png", "Anatomi & Histologi 2/Gamla tentor/2025-06-03/!2025-06-03-0003-UJR.pdf", "Anatomi & Histologi 2/Gamla tentor/2025-02-08/!2025-02-08-0003-ESW.pdf", - "Anatomi & Histologi 2/Gamla tentor/2025-01-15/!2025-01-15-0021-HRY.pdf", "Untitled.canvas", "Biokemi/Metabolism/👋 Introduktion till metabolismen/Untitled.canvas", "Biokemi/Metabolism/📋 Metabolismen översikt.canvas", diff --git a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/1.md b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/1.md index 3412e33..2cbf82c 100644 --- a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/1.md +++ b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/1.md @@ -11,5 +11,5 @@ Var sitter de nedre motorneuronens cellkroppar (ett- eller flera alternativ Ă€r - D: Cortex cerebri, lobus frontalis ```spoiler-block: -TODO +A och B ``` diff --git a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/2.md b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/2.md index e4be3e5..7dbd6c8 100644 --- a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/2.md +++ b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/2.md @@ -1,5 +1,5 @@ --- -tags: [ah2, provfrĂ„ga, frĂ„getyp/mcq, anatomi] +tags: [ah2, provfrĂ„ga, frĂ„getyp/scq, anatomi] date: 2023-05-31 --- Vilken hjĂ€rnhinna innesluter cerebrospinalvĂ€tska och blodkĂ€rl? (1p) @@ -13,5 +13,5 @@ Vilken hjĂ€rnhinna innesluter cerebrospinalvĂ€tska och blodkĂ€rl? (1p) - E: Dura mater ```spoiler-block: -TODO +A ``` diff --git a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/3.md b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/3.md index 9db6188..3fee31b 100644 --- a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/3.md +++ b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/3.md @@ -3,8 +3,13 @@ tags: [ah2, provfrĂ„ga, frĂ„getyp/matching, anatomi, öra] date: 2023-05-31 --- **Matcha rĂ€tt funktion med rĂ€tt lob:** -- (1p för alla rĂ€tt, inga delpoĂ€ng) -- Smak Syn Somatosensorik Motorik Hörsel +(1p för alla rĂ€tt, inga delpoĂ€ng) +- Smak +- Syn +- Somatosensorik +- Motorik +- Hörsel + - Lobus frontalis - Lobus Insularis - Lobus temporalis @@ -12,5 +17,9 @@ date: 2023-05-31 - Lobus occipitalis ```spoiler-block: -TODO +Smak: Lobus Insularis +Syn: Lobus occipitalis +Somatosensorik: Lobus parietalis +Motorik: Lobus frontalis +Hörsel: Lobus temporalis ``` diff --git a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/4.md b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/4.md index 8a7f5b6..8421ddf 100644 --- a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/4.md +++ b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/4.md @@ -9,12 +9,11 @@ date: 2023-05-31 --- ![[image-32.png]] Vilken siffra/bokstav pekar pĂ„ - -a) Corpus callosum - -b) Corpus pineale +a) Corpus callosum (1..19) +b) Corpus pineale (1..19) (0,5p per rĂ€tt svar, totalt 1p) ```spoiler-block: -TODO +a) 2 +b) 12 ``` diff --git a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/5.md b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/5.md index 1913258..e1abd7c 100644 --- a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/5.md +++ b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/5.md @@ -15,5 +15,5 @@ Om jag sĂ€ger ”reglering av vĂ„ra hormoner” - F: Hypofysen ```spoiler-block: -TODO +D och F ``` diff --git a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/7.md b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/7.md index a732677..f0b3207 100644 --- a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/7.md +++ b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/7.md @@ -11,5 +11,5 @@ Vilka tvĂ„ av följande pĂ„stĂ„ende beskriver bĂ€st limbiska systemet? (1p) - D: En del av limbiska systemet Ă€r hippocampus, som bestĂ„r av cortex cerebri (om Ă€n med egen histologi) ```spoiler-block: -TODO +A och D ``` diff --git a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/8.md b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/8.md index 9fec70d..cb0e8a5 100644 --- a/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/8.md +++ b/content/Anatomi & Histologi 2/Gamla tentor/2023-05-31/8.md @@ -2,14 +2,13 @@ tags: [ah2, provfrĂ„ga, frĂ„getyp/textalternativ, anatomi] date: 2023-05-31 --- -Vilken del av hjĂ€rnstammen tar emot- -, kopplar om och skickar information vidare frĂ„n cerebrum till cerebellum? (1p) -FrĂ€mre delen av pons -Medulla oblongata). +Vilken del av hjĂ€rnstammen tar emot-, kopplar om och skickar information vidare frĂ„n cerebrum till cerebellum? (1p) + - Pedunculus cerebellaris - FrĂ€mre delen av pons - Mesencephalon +- Medulla oblongata ```spoiler-block: -TODO +FrĂ€mre delen av pons ``` diff --git a/quiz/ADMIN_README.md b/quiz/ADMIN_README.md deleted file mode 100644 index e69de29..0000000 diff --git a/quiz/db.sqlite3-wal b/quiz/db.sqlite3-wal index 716a0dba826d3479ef32679967aa01b4a29edeef..066729ae280c0cbf731f6d9feb7e28c6f41ae168 100644 GIT binary patch delta 286134 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 DH3Xu7 diff --git a/quiz/pytest.ini b/quiz/pytest.ini new file mode 100644 index 0000000..08d9cd4 --- /dev/null +++ b/quiz/pytest.ini @@ -0,0 +1,17 @@ +[pytest] +DJANGO_SETTINGS_MODULE = settings +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + --verbose + --strict-markers + --tb=short + --reuse-db +testpaths = tests +markers = + admin: Admin interface tests + import: Import and parsing tests + import_tests: Import and parsing tests + slow: Slow running tests + diff --git a/quiz/quiz/__pycache__/admin.cpython-314.pyc b/quiz/quiz/__pycache__/admin.cpython-314.pyc index 69e57a19895825b5cbfe34558850cb68b6f27629..f494497bd12816e91faa07028388ff61a968d17e 100644 GIT binary patch delta 4379 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 delta 427 zcmbQvx08ogn~#@^0SNYZdSnVuH47$I~4V;G}4 zgvpQ|&cGnUFu9shPAh;NDuzS_vA{G1v06khFr+KNl>6XUo+bcN7{?IA?#bf8TEHaF z5XPhkcZobh5Sua_YjSSh$&|}D*_kDljqMh5VqWFs4J_(xLO^$DGEctCBFkL_Viz+5 z3AM?BtQw4&ldV{_?4R?itWjF8wNh)n-b%e4QkS({POx3k^1Bigay=;KVo=QWpoEJ- z2^SKxF9+pZ3M#qFV}FxJ;Tn(9M^=Vl#!|+~7g%E%6qzGaufCye7af`zyH$SB`C)KVpJ9+}k>d3{+q0!uTiHbz@9vvC#zS@DynG0U;tVks?7EtZ)RDo jq5?FW5r~V0fW!x8Mn=ZF3^L!@1Q@kGGXRMqUZ5}l`coy- delta 125 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�P<(5DVvI090RLj)(@kwn*B`JA)FaR6@_^igyyb9uP1(4CB4 zBakm(7)zw${+@%Mfz+PmW5xD(L!@HLLXmnOD*roDt!^yq(MvC9s_`2}sTMqQU`fFo zIw1vt)EQ0>f?aIas`^L&3P+z+fDK3X`Bp=bz0(GZ5B85U#F467RpXEF&lsqWNuDLb2sr)Koi zisMtf1=jDV`awreoT!y(oWy>0p0DvI^vlkZWFEd^bWpB?_`%My?BAHqEvK7}FWr9d z<-ISr7^rUTaQ;-79a%d3;KaQXTMSgUx;<=nRUfKb?QV9sst?tz VU0!yEeHeM%`Ka@s4A$wA{sZ7;W9$F` delta 1458 zcma)*U1%It6vyYz&VJ48&VGO7vq{rv#>BPJYBVmvG)XN@P0W}UeMrVhGHnL8yUCq9 zw$wgU1#Lkn;XEkfLwvHKU>|&ug6}>_5z#WVFM{ud?n4m;@41_eEe+_v{^!g+=iKw3 z-`xG^@Z|&gu%1*Ue!V?=+#QW<>1Fwrv^Dg7#8d|)LxXWLNEp0JoT90eBo;TrOg$|z z&5ZDLG*I9H8G)6M4}Zz8mZFyBF=|;s;*{sm%I;GP9)p_a-EH#b zMH=D#JA_90&5#47?$>HTc%^37--G$&nWuHW?%|ZDsCZ8SBi-JkjWZ8-lO0EMyo~0# z%o$l)z=tAA38jpJd}#-lAb!TJIo5{F)`R$>bAh#Z55X~g-tOS>wzp z4yN5GJt)0ggs<0HzWQ+yC4q@eE)PHDtl_SHE+TyO!+h!)Qby$PZFB;DO}X%OtV^Z` zLC+779%!VGoE7GGBKw&SQhwdt^c{;e&N^(}p)E7mLB#;X6DV&u%%)>JM3)|#`9!+Y zHMo7@cIV;GQmx7={GDdcy~1;J2xVW!`uqldr8eO{*v=R|j}Xp0VG_2~!YM=$M5iX- znwp%Rp)ce12%L&(!x)K?RgC@v=_uBqlwl_OPK)9wr`Q2MWHaQ@E=dE5xj&LG@G^tY zBiM2fV)+Ug=0FQ@GGCe&CIneJj`_!tQDNnEos|`w>RklCh?1JxJG#$^cu{Prhd{>yAd4zG{ z3C1`?oZ?B0#Gn2sMq;`+gDvDRRUBA9ff+o_fo9je^A2sD_s?4Q7mL?V-(22V{?YhFTZEsAO9^2cJLZfijFoyU;YNHzvDxF)gIM)kW6iHQ z6Lf+15S)T`?}rE>_oVJ-Bupw-UbAE(u diff --git a/quiz/quiz/__pycache__/tests.cpython-314.pyc b/quiz/quiz/__pycache__/tests.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cfa65f93e653381aea2e95ce0b573f7521ee7e8 GIT binary patch literal 11713 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 F_zzC}U=;uW literal 0 HcmV?d00001 diff --git a/quiz/quiz/__pycache__/views.cpython-314.pyc b/quiz/quiz/__pycache__/views.cpython-314.pyc index c04a4d9a75ec3462844c81181f3f13734e26aaa7..6bf43488c4b95ab7824f306803db43bb42ac1940 100644 GIT binary patch delta 459 zcmYjNJ4*vW5O$BOp1E8wuA87@jEy1cMbUsF8U-IHK8S*?63GcCKKDGIjfK6%{0BiB zl@wOiTFISP9Nf?FQ3}om3kUY&o5wdZJD;uJt$J@{Ubr`QL=FL72AO zLG)u#3`l53_&){RklIIOEGi%$k?AYA){?QnD0)WGjc5J%1l(&|ghMiB_R7Q7z9Hmp dHPLOI;F8Omh3eSue#O}_i((XRV)GH1`2~DVbT9w_ delta 489 zcmZ8dK}!Nb6n2(f+}4z8T**UJ2Ni2Gs7pl|L13X?if)2hPGOW=Z+59;bn8?wf1)~+ zV&I`$rw&1Ddl01mP!!z?npryaU_Rcw@4flHH($}uXmC6f)OjarM>aoAiQ^#vbGe z)W_cCb@AmHyt5_q)R`Jj#)T){_80K~H9zEu8;(NtI9UN=pSO*(zY}6AiqhUDM zaPeXXXWKaYn0HDQ2WMSOn5kq@xOuO9AQWLYei(=f5w$|L%l4i{lK~iEQ))ghcJYLg z3iOFQA|NI}OhB9iRXD^)cz|a$NbSx2s#Pr4=n(-hI6EZTp4pRnBA*w|3eB~iYlPzL zM_btN6hfTuDqz~R3dlVFQ|tPHP~itsya= 70 else 'orange' if percentage >= 50 else 'red' - return format_html( - '{:.1f}% ({}/{})', - color, percentage, correct, total + return mark_safe( + f'{percentage:.1f}% ({correct}/{total})' ) score_percentage.short_description = 'Score' @@ -159,7 +186,7 @@ class QuizResultAdmin(admin.ModelAdmin): def result_status(self, obj): """Show visual result status""" if obj.is_correct: - return format_html('✓ Correct') - return format_html('✗ Wrong') + return mark_safe('✓ Correct') + return mark_safe('✗ Wrong') result_status.short_description = 'Result' diff --git a/quiz/quiz/apps.py b/quiz/quiz/apps.py index 2a08bc2..69b56bc 100644 --- a/quiz/quiz/apps.py +++ b/quiz/quiz/apps.py @@ -13,10 +13,11 @@ class QuizAppConfig(AppConfig): Starts the auto-import watcher in a background thread. """ # Only run in the main process (not in reloader process) - # And not during management commands (to avoid database locking) + # And not during management commands or pytest tests is_management_command = any(cmd in sys.argv for cmd in ['import_questions', 'migrate', 'makemigrations', 'shell']) + is_pytest = 'pytest' in sys.argv[0] or 'PYTEST_CURRENT_TEST' in os.environ - if not is_management_command and (os.environ.get('RUN_MAIN') == 'true' or os.environ.get('RUN_MAIN') is None): + if not is_management_command and not is_pytest and (os.environ.get('RUN_MAIN') == 'true' or os.environ.get('RUN_MAIN') is None): from quiz.utils.watcher import start_watcher_thread start_watcher_thread() diff --git a/quiz/quiz/management/commands/__pycache__/populate_exams.cpython-314.pyc b/quiz/quiz/management/commands/__pycache__/populate_exams.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f691ae134b4f36ee790f888b3fa157400842da53 GIT binary patch literal 4206 zcmcH+TWk~A_1Yf4?Zg3xV8>3zA&DK6*ladSNLWHcfI2ln-U$qp21bKD3B!&(W@fxB zSyk{+X@mA-gVZkJi&TAOznXrPk4k0LN~O|DwKssRDX2*BQ}ttwLYGy4+H=RABxF&c zEA>jAbMC$8o^$Rw_dL9_!RaowJ920Dgo#C=vP9f7`Hp}KRq#}}eF<%f^g(Jtrf+TY!UOx0bnN^s*n$)@Wl}6OQmhmK z7c{HZx+1fqsc&^9a+()7G7k#LX-X2uW-AR47W%I1->Z(c!|QhQ)8?xf(UZ61_`Hl-Ri zuOj3^{lZ92XV^;@L2udnG?-Zs^$sVrd)5`VeH66tz60f=9h94PQVlT_H=W?Sfx!vB*T2q(YS=hSb*=B{7;V*gx1)BH z{5?W;)P|COsKGNR?}DFus%MSflG>@`>rlUk&I{~%7a{G{e>v5w)4lXXyd;Kb6WvI; zHfl~a(hZacR^1y{A;Ovl!JBwsS$R*M*brk$Eyi4D480rTB=36t7ymy*-4@!DA~i)` z_`E#o;2tmd1*`>+FK>l+9(&=qHlaOxF0Q>ghy&U~TXQWs9h`YX$JZgXHD;i^gY|fC zN3?sB72L_abx+@~!T*a@nrW8{QQK%Yd|O`e4&GeXUdQxT1*f;aBCouqpIL{LkMipl zNd0&2t>1!s;B~q8JM^f~VLPInI}nMcaKLKAf-+DE0uX^gbjX-If)J1Pnu3To^t8=3 zkLFlK%<^P6nSjbH3gQe;_Nzo1YPM<`P4uhI3@4{0K97s_Y@kMdNh|L9flJT@;ELa4 zREZp$<)AQg(`-RdorSb)g(|3?PBP5PAf6A&*%z z(>y0+C&)+)EX6;OxLspHCtd!Z#86+m1TY=$7Nu{0~^Ogg%zNcVY6_7 zS>Fq!7kTBJzI7Ocx-c2T@N%vo2(y82(24~`CqMT0f5Yf$l?Z%Mt9bT2msVueJ;N!C zC^0qVREw-+;E-y*DDhw?v#eYaIMq6Nd~EE{73uwQgn!7vZ{rYF?F9o9=Ga zfjtaLV|57(RJ&$M>_Q2bY}KuKpl+9px^r8B;~rD`R1cPzR}WPjCxf<5;Fv85001@@K$ z2TFkhxA)u)98m+I)m9V?KW#+5j%q*h`RCk~U|Z4Ve-a8Wxyq5@Qe=1~^7j4EKCstZ z@hD$m?H7R=lCbfhF57Myh6$A7rB-*xnj!xH>U+ za6bHe3-Yv7Ji*1Ld!CMYcg5qq?!4wKdqO2oXtDEG(Vs@k9RsC~f#TqI?+|~n|Ji=~ zgW_A0D;>w~dX85uC>UC7EeHCFfxdZLWozs8eb@Fa9=yLbQt6D$Cw@Fp@dmC>T$@1iw)sjn-$Lij=#6OE-&^wc zR*i;6vO@MPT&^NRysOeRSnS*b=uq2(wvL;^4WaJFw*F%9O;{fYKM2Rm;lWaPupAyK zg-7ljDuzc^!c;kYvJ^hK5A%m~$uYu003E;nEoTmTSG#Wkx$j#GwF9Sj418ViuL7}dov7YeLU-)UqZeM_Y$*e1?# zLSEX3^FcGi$O^0QX@*r4i9cI_Xa$NUhDi&M5g>+@G$#lQgKNQtML33=neRw%!&k!p zg|rW}!AQo5=Iak3Mk~&vR}MZW5(cxgitu+8c4~x2d)%s(VKQPGq_t+wv$+{DmKG&0 zww@qjXWclA}R{NW*CI4D>#aX= 2: + # Try to find a date-like folder + for part in path_parts: + if '-' in part and len(part) == 10: # Looks like YYYY-MM-DD + try: + exam_date = datetime.strptime(part, '%Y-%m-%d').date() + folder_path = '/'.join(path_parts[:-1]) + + if part not in exam_folders: + exam_folders[part] = { + 'date': exam_date, + 'folder': folder_path, + 'questions': [] + } + exam_folders[part]['questions'].append(question) + break + except ValueError: + continue + + # Create exams and assign questions + exams_created = 0 + questions_assigned = 0 + + for folder_name, data in sorted(exam_folders.items()): + exam, created = Exam.objects.get_or_create( + course=course, + date=data['date'], + defaults={ + 'name': folder_name, + 'folder_path': data['folder'] + } + ) + + if created: + exams_created += 1 + self.stdout.write(self.style.SUCCESS(f' Created exam: {exam.date}')) + + # Assign questions to this exam + for question in data['questions']: + if question.exam != exam: + question.exam = exam + question.save(update_fields=['exam']) + questions_assigned += 1 + + self.stdout.write(self.style.SUCCESS( + f'\nSummary:\n' + f' Exams created: {exams_created}\n' + f' Questions assigned: {questions_assigned}\n' + f' Total exams: {Exam.objects.count()}\n' + f' Questions with exams: {Question.objects.filter(exam__isnull=False).count()}\n' + f' Questions without exams: {Question.objects.filter(exam__isnull=True).count()}' + )) + diff --git a/quiz/quiz/middleware.py b/quiz/quiz/middleware.py index 9852285..c8fb0e1 100644 --- a/quiz/quiz/middleware.py +++ b/quiz/quiz/middleware.py @@ -1,4 +1,4 @@ -from .models import User +from .models import QuizUser class LazyAuthMiddleware: @@ -14,8 +14,8 @@ class LazyAuthMiddleware: request.session.create() session_key = request.session.session_key - user, created = User.objects.get_or_create(session_key=session_key) - request.user = user + user, created = QuizUser.objects.get_or_create(session_key=session_key) + request.quiz_user = user return self.get_response(request) diff --git a/quiz/quiz/migrations/0004_rename_user_quizuser_alter_quizuser_options.py b/quiz/quiz/migrations/0004_rename_user_quizuser_alter_quizuser_options.py new file mode 100644 index 0000000..b1edeae --- /dev/null +++ b/quiz/quiz/migrations/0004_rename_user_quizuser_alter_quizuser_options.py @@ -0,0 +1,21 @@ +# Generated by Django 6.0 on 2025-12-21 19:46 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0003_question_file_mtime'), + ] + + operations = [ + migrations.RenameModel( + old_name='User', + new_name='QuizUser', + ), + migrations.AlterModelOptions( + name='quizuser', + options={'verbose_name': 'Quiz User', 'verbose_name_plural': 'Quiz Users'}, + ), + ] diff --git a/quiz/quiz/migrations/0005_course_exam_question_exam.py b/quiz/quiz/migrations/0005_course_exam_question_exam.py new file mode 100644 index 0000000..7248a0b --- /dev/null +++ b/quiz/quiz/migrations/0005_course_exam_question_exam.py @@ -0,0 +1,44 @@ +# Generated by Django 6.0 on 2025-12-21 20:10 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0004_rename_user_quizuser_alter_quizuser_options'), + ] + + operations = [ + migrations.CreateModel( + name='Course', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, unique=True)), + ('code', models.CharField(blank=True, max_length=50)), + ('description', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='Exam', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('name', models.CharField(blank=True, max_length=200)), + ('folder_path', models.CharField(blank=True, max_length=500)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exams', to='quiz.course')), + ], + options={ + 'ordering': ['-date'], + 'unique_together': {('course', 'date')}, + }, + ), + migrations.AddField( + model_name='question', + name='exam', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='quiz.exam'), + ), + ] diff --git a/quiz/quiz/migrations/__pycache__/0004_rename_user_quizuser_alter_quizuser_options.cpython-314.pyc b/quiz/quiz/migrations/__pycache__/0004_rename_user_quizuser_alter_quizuser_options.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8af889aa0b1c14e75d56b1a8e7ea4bda384a98e7 GIT binary patch literal 900 zcmZuwzi-n(6uz?)*LKpTrHw#pl~OfJgc?UsiKRkfqWowmC&7Z+lM`QCSN@Tn&qZX) zP%-l-u=1ZUmM)EMWkO>i@H8bxVj=)B220G zVvhxK>1iMs&+zNK9BIcrHn}iar8E(6d0pxX!TnAuSVAeUK%TLhx(prFbmE-GCvN1q zCvdRiv2!P6p5JwYttf^FNA;F(v&Y~-tJ{=kwNN!u=p&ykbyZC>I+;_m_mQjjnQaDu z?0jx a{M$fy*r+6N4{0g_CeS98J0We-3Ra7EFq_!BWOhvh zryQ!Dd*s+-d!whexAatwwu@wIF0Is4Z9>h;^S+(a$py(>m=TZ{tYZUaJ#ms--3M!5 zL3m~9Kg|+}tM8(~fMpOY<0oMPVyM8sLL36SGpE>vU)$w8Xtk z-kbcl+yLYKV~k^`=LU@&vwTa=C=Q5cPl|6PTpE9Kz7#%TZsLTw3vbS$1UiF~g_?ar?Gbdr zj-x5)w|A)je!tJCJZIL4g{h!x%L`Okn$Jo!q}FXyDHE*PSfjyBqSsWitvtos)Nf&; zt5tmm)6f)2L#<&dvC(N^^|?~TM#bKsa^29M)nTm+tqg0PwW?}7CBu*_8q}~= zCi*68_AF0WuhBryrJ~wdz88gIeyOOMmK6v)N-D4GxT;xHDn3_hRMH?q8eBK48Yar7 zYH#RA&_`4*yQDlAtgM~jv%0>MbLX-m^_zwQpB4;TLj+eDNtc6Qo786#z|xJ%j^~<# zKJ1cEY_o#x4NS746A57*O1-8-z@k3jX)!KTVm_(AuUk7Hf_FBX^|(AGqh76&a{xfj zvw;;Z#1(M9cupuTS&u{Zd*{;A1>DuE803%wFlyBSRp_vytn0+GtGa;=lPzm_6B`;f$~v|P>jxT~bBg0W z`$QA=)uf?2dSw>MW`Ug#_0Mgnq$e)p=e8>j%|ou2^&0N2Fx3?U3nsyO#rOzHOxB*L zM#ZFpwnlxPu*++BD__H!URJBu3=?+bSvK9_^-YwAoZnE~>QUG+LbBYm zcC_5)Hj%-DGr)z(SwBMwe$91d&R^uaq7au~-0KE7|47?6-tvuiqNzWkiEp;r$;++e z<)5VeOyhHN$F+mAT{5J6g$!JZ{_yH zbZcUI&pw#A)rh>`iJsrxYNuyf=^5CXW?Q>KIUYXp`2B%yd?1A6+lBC3>PjPW^|qKWIwpVhA-UZ@%dAXOZYk!pwVeBFB-gJ;QeZ{pf3@zkh0_UcF|fjOp*~=S uzFTgy-6nTHWmmnGhM_C+Jbx&2{N(T4r6c(wFMktgM>5Sw=5G!Z2jCyX=t%JZ literal 0 HcmV?d00001 diff --git a/quiz/quiz/models.py b/quiz/quiz/models.py index 6cdf087..6030d88 100644 --- a/quiz/quiz/models.py +++ b/quiz/quiz/models.py @@ -1,15 +1,45 @@ from django.db import models -class User(models.Model): +class QuizUser(models.Model): session_key = models.CharField(max_length=40, unique=True) created_at = models.DateTimeField(auto_now_add=True) + class Meta: + verbose_name = "Quiz User" + verbose_name_plural = "Quiz Users" + def __str__(self): return f"User {self.session_key[:8]}" +class Course(models.Model): + name = models.CharField(max_length=200, unique=True) + code = models.CharField(max_length=50, blank=True) + description = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + + +class Exam(models.Model): + course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='exams') + date = models.DateField() + name = models.CharField(max_length=200, blank=True) # e.g., "2022-01-15" + folder_path = models.CharField(max_length=500, blank=True) # Path to exam folder in content + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ['course', 'date'] + ordering = ['-date'] + + def __str__(self): + return f"{self.course.name} - {self.date}" + + class Question(models.Model): + exam = models.ForeignKey(Exam, on_delete=models.CASCADE, related_name='questions', null=True, blank=True) file_path = models.CharField(max_length=500, unique=True) text = models.TextField() correct_answer = models.CharField(max_length=50) # Support multi-select answers like "A,B,C" @@ -34,7 +64,7 @@ class Option(models.Model): class QuizResult(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='results') + user = models.ForeignKey(QuizUser, on_delete=models.CASCADE, related_name='results') question = models.ForeignKey(Question, on_delete=models.CASCADE) selected_answer = models.CharField(max_length=1) is_correct = models.BooleanField() diff --git a/quiz/quiz/utils/__pycache__/importer.cpython-314.pyc b/quiz/quiz/utils/__pycache__/importer.cpython-314.pyc index f18029b3feefd24a1d974d35ed9761fff9ec1417..85cda3895223202df33814295175d960d3a325a8 100644 GIT binary patch delta 3038 zcmai0eN0=|6~EV?*cdQi`}zCX7=w+$j)8y^zDhv>Lf`w#>N%Y+DS?W+fZ~O8QIwZX zl;oB8wV@l0s|WtnKVBMuElQJAcJHI?Ro$w@Rs(<)a0<@ANjb(3I3uU$Oq_%>^Rj>( zzsS4~EqKLh!3pCGwBlRFqtHgMJv(KZh0u<#nR_66))IvdeB9Cjo%oz3%BY$tJMzrR z#nnB2D5%EVxYcU$8K{Mqao%0TAKQ-3D>;T&eJT1e>x3BnkFWthXLHJ3e0|u0?|SR; zmR^ZhZ4C6{yv@fp^7Wk(F(43hysjKQ)+k1Iles$FW#8G>)M_q`qA1kRU6|ZXnJtJwnZt zvuxsPI$=paCJ9|375*o!!B_fW=oR88jZFHXhVwmIn=wd2YI*H;CD+7C25Nb&*fuxl zs|6ebT-^vW%m}qC)$2!$!zQ6_*eukufUgaAueR|SK2GU0cYM&K%!x=@m_!Ba zfUgVd%Gr7-!+kVLke)qr0CJY@2mSnQA_Yb;lFt91v#JL~!9NVRG~1mqX5#T;AZX-G;e910k79VFTpCGuw5*z0Ff~>l)%idX zEQD1f4)__yD%f}{Z`nmUZnl(ba-U2cqWgjqG@p7C@!L3edGDkxEa6G&@mkBdVZrDS z0Aghx?m}FRYjOdu8Gqs*m&y(v#KFMM!?J_s1n804Kmt0zl(x)ms=ustNE9F@-g+#s zfxYnHu}8*7%A?4bE}AF79-*1yX{JVeW$)C{vc`ELKm#~$#I6DDvWJXqfSeEefSqe0 z3`H#g%W=GI*?$Ml!UgeKs!2VS5QKPaI&tz_(YZtn(WMdPUC@+W>0~U4NaDaD^mT$> zSg43a)A4j-KJJwi6kki8LaBH`w)dIn{OJn!(Kh#dn>%(|KX@{lPR=JBO^)G2DxI85 z&L$k~1?2-X`Ucq`D5$o51=)#YEMAZf4!4t4WhbNaaS|s!6FoJTPN5|_xN;^r7mK55 zA)5Xs3Q;Q3g5t!3@RSldLx;)Ygb6x=-whg+q6Hnsp9b}i$D2VB!>`UHv-0)@(vc_E3?dufP*_LM+bJ#-_|`~Z==0ieTlvNSq3S5S~Bal(C`Sg@+2 zg<%oxY4j|GK?>iaa1I~ncslZ9q88LN0VE43ohnq)%}$pRBa(lFuS)b$3Wds2nx@6H z%IH*t=~8eS{RCg>u&TsvfbkTIKkZ=fKRTN0-Y0(XPhv(!|0IgKXR}S6*Wg020%&!M zvb@o`VQkA8+b$;7jG@J0@2+6J6+z!?WQC<{ZHl-{sS5jsqKx@tk9P z%`vewLgKo1FO4pI#F#U?@xn#tI^$bWU7mdR$U8??pIqykSl==6*G7M4D9?H>4z060 zRz_Dl|J=Dh?{8nO{ENeVR|(vKq8d1xe^LE<^@gK2=jhGbJoyICMfWmWlmlnqJs@=& z&WtSW%M2DZfOWk#cYbce9?IE6dDfk`HQli`EDw$5t=@|rZ-?FttsJPZOvI*H>`a*YhT{rx}!B@WO=-`)6A&khH4Vl z)_)IF*wncS(_+O^^QI2_yUt6drTBMU17=PFOs)%~x0oIG0jxC@bjG5T$j|ThVs&@- z!pEdU-|w1az&qxNZg90;Nz|*>Am;+_Ro7BRMpyw$pQ+S=i4hnBjP*S)+prDvZ%#rhl-lmo;1$x_{UlH&sOJ`ABlcH iV>Xz>!;iID=qLJSOisVZHX|}Utliw9fqGO&#{CxrIO<~n delta 1794 zcmY*aS!`2B6rFkA6UTmDH)L5iyWbmke0bUw~Kb7tn=J2Q9Q z``|d7KLRzinvxYBvo|fc9;qpAX?*0~4xYdq_(& zr;S&wg?To?wCl+{HBE(=ay6otmx=9iY!MT?FL4H~_+gy^f9h027rx{Sz#9C!vlhCs z#^r=%xXv|H@2Rupy;4kQIXUO$6`YS(apk<0tKeC#l9x8fS{NVj*JjSTo3eGN%uV|lw+ zo3~5+_WT4y&K68c2^5-&5D5U%<*yXfn;mabxHG4#4@+^R!3`Vmkp|Of5ns#|tv*VQ zB?|jLMMn-&!s*+XyyLftMSt_!suyvhcJV@)P8e*&XX<=9OCf<_D^JyCs(P#imDdE< z7WhjWY{Nl?jR|&8v%w8QorST&63ONY z{AE+1irKsz1Dz5lh`}-tA}QpX97J1b=!9X6ZhL@kd&pZD4ZT)iTW|Jx7OsEAg&NgAgfNk6-y+9XgC#*#u0sV zXa{x$%-*arDojPv)2Sp15!ElrDreI|l2$ZI(`n;)OQ5=FBr6jTiX(qHJ|38YNAYrC z(3NG=iD)DxgySeYi3GwBxnqDF6hio1Yj3F$4b#p63jGugQaFnLX`O|~@b_&FR(wNH zNE*pNC29DI*NltX&H9T(5R2SR&i63L8AmcL?Te6kF2|lub6v^8)MHEi6Si!j{y}5k zV{iWx)^TcgP9ZgGax&oTSq2Jc(L<$mPPwATyE{)a{S20M^)d;FC%W1Wog?1gZ5^?I zuWiFDxM@%jdehM~+y#En=qP&Dsfo0>#!B#(s$WjbPpp|@6_su?8j8AJfg-vK7M}lDh#3RoWrt9U||lHjS7h_jI&#&j=|uiQG~=YLVRY)r=Y>_YE?_ z@k~{$BwIWcpNl zo3nHRJ;H2BRG1c0q_8IA>6ug(hS3EgC#g@ob)ZsqosdZ8;D8*~XNzakv3J(&iIXMC zd{iYFMfB%|_EVtSL3A7FI{sq46Mlzpudlb$-`L7V8U4ns>}5<)x6-JFCh diff --git a/quiz/quiz/utils/importer.py b/quiz/quiz/utils/importer.py index 73f472c..8e22103 100644 --- a/quiz/quiz/utils/importer.py +++ b/quiz/quiz/utils/importer.py @@ -131,8 +131,15 @@ def parse_markdown_question(file_path: Path, content: str) -> Tuple[bool, dict]: question_text = line.strip().replace('**', '') break + # Return early if no question text found, but include has_answer field if not question_text: - return True, {} + return True, { + 'text': None, + 'options': [], + 'correct_answer': '', + 'has_answer': False, + 'question_type': question_type + } # Extract options (pattern: "- A:" or "- A" for MCQ, or text for textalternativ) options_data = [] @@ -184,11 +191,17 @@ def parse_markdown_question(file_path: Path, content: str) -> Tuple[bool, dict]: options_data.append((letter, option_text)) # For text-based questions, options are optional - if question_type in ['textfĂ€lt'] and len(options_data) == 0: - # Create a dummy option for text field questions + if not options_data: + # At least return something for single-option questions options_data = [('A', '')] elif len(options_data) < 2 and question_type in ['mcq', 'scq']: - return True, {} + return True, { + 'text': question_text, + 'options': options_data, + 'correct_answer': '', + 'has_answer': False, + 'question_type': question_type + } # Extract answer from spoiler block correct_answer = None @@ -294,10 +307,57 @@ def import_question_file(file_path: Path, base_path: Path, stats: ImportStats, f stats.questions_with_answers += 1 stats.by_folder[folder_name]['answered'] += 1 + # Extract exam information from folder structure + # Expected path: content/Anatomi & Histologi 2/Gamla tentor/2022-01-15/question.md + exam = None + relative_path = file_path.relative_to(base_path) + path_parts = relative_path.parts + + # Try to extract exam date from folder structure + if len(path_parts) >= 2: + # Get the parent folder name which should be the exam date (e.g., "2022-01-15") + exam_folder = path_parts[-2] if len(path_parts) > 1 else None + + # Try to parse as date + if exam_folder and '-' in exam_folder: + try: + from datetime import datetime + from quiz.models import Course, Exam + + exam_date = datetime.strptime(exam_folder, '%Y-%m-%d').date() + + # Get or create course (default to "Anatomi & Histologi 2") + # Extract course name from path if available + course_name = "Anatomi & Histologi 2" + if len(path_parts) >= 3 and 'Anatomi' in ' '.join(path_parts): + # Try to find course name in path + for part in path_parts: + if 'Anatomi' in part or 'Histologi' in part: + course_name = part + break + + course, _ = Course.objects.get_or_create( + name=course_name, + defaults={'code': 'AH2'} + ) + + # Get or create exam + exam, _ = Exam.objects.get_or_create( + course=course, + date=exam_date, + defaults={ + 'name': exam_folder, + 'folder_path': '/'.join(path_parts[:-1]) + } + ) + except (ValueError, ImportError): + pass # If date parsing fails, exam remains None + # Import to database with mtime tracking question, created = Question.objects.update_or_create( file_path=file_path_str, defaults={ + 'exam': exam, 'text': question_data['text'], 'correct_answer': question_data['correct_answer'], 'file_mtime': file_mtime, # Track modification time diff --git a/quiz/quiz/views.py b/quiz/quiz/views.py index c26475e..1d66f56 100644 --- a/quiz/quiz/views.py +++ b/quiz/quiz/views.py @@ -7,7 +7,7 @@ from .models import Question, QuizResult def index(request): total_questions = Question.objects.count() - answered_count = QuizResult.objects.filter(user=request.user).count() + answered_count = QuizResult.objects.filter(user=request.quiz_user).count() context = { 'total_questions': total_questions, @@ -17,7 +17,7 @@ def index(request): def get_next_question(request): - answered_ids = QuizResult.objects.filter(user=request.user).values_list('question_id', flat=True) + answered_ids = QuizResult.objects.filter(user=request.quiz_user).values_list('question_id', flat=True) next_question = Question.objects.exclude(id__in=answered_ids).first() if not next_question: @@ -42,7 +42,7 @@ def submit_answer(request): is_correct = selected_answer == question.correct_answer QuizResult.objects.update_or_create( - user=request.user, + user=request.quiz_user, question=question, defaults={ 'selected_answer': selected_answer, @@ -54,7 +54,7 @@ def submit_answer(request): def stats(request): - results = QuizResult.objects.filter(user=request.user) + results = QuizResult.objects.filter(user=request.quiz_user) total = results.count() correct = results.filter(is_correct=True).count() diff --git a/quiz/tests/__init__.py b/quiz/tests/__init__.py new file mode 100644 index 0000000..20af3ab --- /dev/null +++ b/quiz/tests/__init__.py @@ -0,0 +1,2 @@ +# This makes tests a package + diff --git a/quiz/tests/__pycache__/__init__.cpython-314.pyc b/quiz/tests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b726bca9e2bb1d5cdddccd08c746adeb40496637 GIT binary patch literal 158 zcmdPq&M4u=4F<|$LkeT h-r}&y%}*)KNwq6t1)2r2tr*1k#LURZSi}ru0RYwrB%%NS literal 0 HcmV?d00001 diff --git a/quiz/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc b/quiz/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d033266378f00dd39066bfa3a532482d068e723 GIT binary patch literal 2773 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 Lec&WJ$VvYfhX%Hy literal 0 HcmV?d00001 diff --git a/quiz/tests/__pycache__/test_import.cpython-314-pytest-9.0.2.pyc b/quiz/tests/__pycache__/test_import.cpython-314-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb82896346eb189b5d9275b200d566b007b9a994 GIT binary patch literal 58529 zcmeHw3ve9AdFC##3k((uEM5d3pvd8ypzt6Fz6_C)_z*+NA_WbprDIcEEr12M;sU#v z*%e7xa%9UnQzA?S`~ScH(GS+v1Uxv79z7VpxWnV=rxWX_ zFe5iB$~>NzJSRPx_mC&;W&g_#mS@UVdJg*1{+0YnMY;l4$_`Z?RMHAdD?e0qFpv%$ ztWH-S45ouD&3CBgU~RhgU??40>3OE2SG8_t|LBb_p-Tr+mabKnAeVgvueXDTaK+*dyn)`iO?^UC=ptH7gv*umnX|4Gi z>dhmq-kh&Dnm*N9o?D!X>P9Rw9Mw+`B+raQMl&%Z9Z!zrn&QK$q@ETDk->N(ma9I* zQjeyiX`{6)r#xk4uXr*=#q!=juQ_x#6Np6d4nBkTPxGY9PFAMNn>|Uqk!hb+w%C*Q zYvl+lG#|oB8vnGSRUoX=Fed4MrXZ}=&`;^07C=~|;VIL#S`cAKt3g<&)u!vio^*p2 z!gJQ;8js;Q4x3Nai)W0F42|_rqDoJUGc`G=J|ETN$&8^s*=NKD;?WV+>Ic>82X!#1 z5949`t%sp!s>WZr#}mByeSCf|;W1L)6XN;_T;YdSw$)4xd#wREgV$PLqm_^D4f`JV zj6S`=%sccgk7v2(gwHD5Ykj^}0{-LEXbJnfz1s5QYrD&~nt3&!=0BlW*?QT8V4d;* ztHj6i5hM&f;J8yTKR2DN@`Od(triX}c9g4~z1(51=GQ8uCvwm4v-+sBtWA36RE1Wl zDdCDvZ(eHUiF)gGIRqZh(eI}dt1 zqx-Gr9XjWDUa56fS&Ya7VIM|h#qn395eZ6dbAM{ZYLiw|GH;cY*U>`(tyT+#1CAc5 zo<|Q=TQyt%c@L!&^wJ^pQk|oh>KA&6M&8j&4US$ar(VLhJ>NH}g}q5D@5?u-(a}du z3vZIko3tpem;5IdS@TTTZ+)Sus*~lT|1lh7bJY*TT=kFCHnR3A`@Vot=={!__#f6S(iHy+*hRBL7eb+xy*2h!0YqeuNj z^wgF%HKiw?AJnhBITUSE+5cGjT&mOPA8k{kBhhqnIQ~pvAexT#s9QE~+0wqbtG#Pm zfGRnDDweiGHv~6CDc$eaMhzpTr`2^EQ|Gp|8kD&0NY0mzolU>rGRt#N z#x4rB=2fwsSFt@;Yd&{`KS|EJHy1b+HLTMbi#ds)OJlimbZV|L$@nj{ArTv)s<%+E zBd`9C@cKV;*_$hi8<)Lf4btp;^BMY~&(`hMB&-PW}(T-&{yUE506cnL~1OYqdq zR2b2~C(Ve;%b@Cwl(MDOXbrq*)wBa=WjXJjv7kI$t!4TWyhdZS`6G6@S|@%=Co~`z zcrhJp@GyrX0>A}fpv4E;-<>%Q7X z-}%a)tnfUr_4oF_z4YHa`JLmF%MXt$OV3xow_J7_4!YVGZB%} zChQznskF!=M^NvzP~+yPm0lvEJ$i=!gH9KjWOwnLx)l?$V+%X+fwda??I!w7}@gTD44-5=Bb*+`Msv1q5m?$mvRkuBi7Vx^ia`s8#o6AKf_w>VAHc z@{op9F$BtY%LvrUFd)7?6N^CZ0Z} zZbtz&bX1K#uMS>$EAbrImzb(w zdE@j*6xcyLkx&iX^3-!z-T+20VyJylBbtb+r|CQ?Lka2Uue>Q?2^N&ln@NL59N{R! zkxYLg205XY>5J(cL^zu1BXA@gkE$c_Aw7!J{XMFcu|JvtN0>3((1krJx<%d3Fb17O ziN|!}b{He66CA3CC?J0ANQOjPg%7He6ijcVfRI+5fMUIw0to3mW956=_UlXOl%!m_ za`dLYoKA^@)mKokk^+?i0Ja1#EdF0#N4W^e)i+YmO2LB^kdWS$ z!PNKTN^XtB?pg0m0iU>|1fN)}AT`B!68HddiXp}+sviKS2zI9X(t$U@EOuXfVLa45 zqnN)*rJFG{bUH1?rzoSyB}XQdZg3J7o7iz_QC2}P9_pA;%-^Ka!PrDPotENLlu_i8 zBNIvoaVHCG;@sFo@Rj;6;l;{hxhj#s#QvXQZ`=IE{($n!Ws*PT(|n`H!hR;q8MH(s za}+!W!xdOcYmPrA33L3CAOmcn^8jlk#O-;adH1|RnBovTS2EFjr9yTVoP$}*jlzEW5MO?Er8JBXuG-I z#s*9vJo7mWinQ$7Xgt=1XBg&wG?pNkJ$)vrnmBS#01Fxrb*|Q03~d%ZIksl9Fc+?KE-Y~?y8EjDa%qG|qer40t_ZAj4DDd?hL3kBT> zE?4Q>D88Kn0%vt-#(DHz6zry8Cj}2v@CX8<3W2~y`Fgc!mi3DDaYXcS?%{bZTaNPk zs0rUiK&w{`TfLUMtzJI|>j418!Z}l@N;Ua!4W=rbGf$ud9 zNq@wI%|daf*L9xLn!=TsNf#XtOEW1cRS8w9Ue{U7(oAGZRa`7?-^pF9?Xy~L&3jf$ zO|iILo~S>^u}V@8v&x$QSBY|VKWUXGO63)^zNVDSTVb`sAr@D(DlHIJ94kcC38Glp zA=NBY1*~iNtdVsGh{dZR62}UWf@oZSo^=15??#YxV&7*F-)HmjZ%f~2eaXHM%5C#`=N*(zKx@z%3smfC=Fvkn zR?k@f&G#7Zvi@DI8Y1__V_j?&7iy_okgLW=Ox;s;16JYqGWdST0#dcG0)nL$(eXPTP5V{MWWKuH;Rt{g&ZM*rlzCiP?U z?(0w`XR7G~X$?xm4(sbSy3Qz`4vKYBKppBJfb&fpBU;D=dBmj0yvy+(LJ-d-zpj0n+j=V_@iE-a7ywRGLhk!v5hng+!|qGR zzS;V(Ba;ogv&!yQ4ZJFoA&OoN?VeHcev@=vq)pgoSC!o;#;;5vUQlvc*)1UgcSdjw zk#ZrqR*CM%mleEu5R>vfl`1wd&GtnLn(ZEm%DGhRJw%@AGb`$HP zCHG$+Y4wm#=gAdsSw6j#_0h8Xu#XxS`UsGsAhabC7n;l5ii81GEn{mKiA$ubBx;fL zNJ)_~L(9G^QA_1)ZO=CK%3GvWD%41+%u$$fMb#8UIkq&20bNNQP*${6-J3iOdCl{& z5p^$=S&1rE<0{Ue57?$Y4^dKVD@u+Jdn}A`J-^6a=H{l{ay_!T1QoPy>)Z(S44d8(|kS!#Fvdgs93s zJ(`5YS<;A&7)d>DoHnaLP8U^GJQ;7Y)EYFVXceT`T1x1|rAD>+z3v9pF{Ey+&e;Ht z?=30T23y!9VSEJd>5o(J1cJ)}iT0f%*?T$YO7SvEmXWWvR&H}y=fdx#?I9vTllG^3cmNu-dv9-U50@hO&5 zWRoKk%0t&etyyJXcFW%B_I(s--!~rGH=~%pNu-b(C7n)7@hO&5WRoMfv_Mkhwf1-x zj73>DsKSjEuIZVV9h+4$+hGfmjsAT!n^LxWx{lrMKI{;#riV7@e9S z`7M!H>`97JsX6Zs6L#G`2l`X*L#28@0>hmyT<)L?2f?1$6yfGMP=t9UK2LZ0QKLwCzksXW4P5mWygbw~ zG_2u1M8jxVY8-3j%Muy!T4t&S*G@Y23Av?8_2{k>$xsWLNCd-p^Y0@ zFlN(8a#>C2hI3m;NLTiN>)OF|kJjQcS9vY9E;O@sfgUiDP7WkZi(MnPspyJOkoOH* z3;qHDNx-VvTCn5;Sqr$X=%$NLXB7%Un`RXAH>qr5>^7ZFOYtenD00aWT$zP4ev7Sq z^Vg@mLX7EDC{k{*An0*?4w+|@KN5@M|tP$N|!N8rRDykRG^&Z zl>gqkXS3z3mmat;dnr%;Thtn`;+Cxemosa?6%K2_#-9^^RE=7bWa!|k@N4_+nwLMS zMJ4Mk?vDyyvhJ=wDz@ye9D9(_`Ige6wIZ9Oer&ieHZTASg!piDC}w2-7|Wcr=Gzts z%pXu+JTbu6{8gX$1S`>z8X0<_yL;!^E!%cJ^Gv{mo%*;4mmy9Av5}FeN+m66F3Elm z#~?NBkEKpUq10E(T@cs_TkEl1eGKOZ+(4-VbK)V!YV%t;yNO=Tdtj~>dA3wk5|_@| z^o}zmTuqNu9^t!%Mr0O-q#*B~5TSC}1gGj5p|aMMP`SIVSXO40&Px?mZEQhbUsid=GJLg~EY>U0DoeJGR?XE$AYx9lG4_0=ulPxyY=AV9sd_tH0m-ifgLWTMcxK!rRIrC+ViCB{Ys@krC6=nZz z*LCbPZ?)nXxdS_Q|8-B9F0q-Sz6DzCVGispGdlBZuiI*~XHKgP6Mg9LrRi=3zM3vD z)QahplR9}xk%p|$eJ(njh)`!)ZqTR~ODc4`mihtiE#Ry=wtB4o09KDX>&^;}u4dt! z|H^-BbF9;x2}Vq)i66ER(`}i$)iYDKh6PhMW@PqrV(R96FB~3V16t$g$4%3!Kb)s& zReiV`rd3VH-;_+Nf+bC>gxS|^m{tX0d{-L|I!pxbglSa(rd8iA=(BteumNq+LZ8vh zVw$5_eU@*u7l6^;!kgvjC1$kOJjU=|;SGnmyNQp$C#Zo{6I#Ef9lc3xz#-{S=D(pL zL2#&_u*nJ`?lR5rEw8txSrzn`Ew{H0Up2z-?SZ%s!iMMP!Zy-~AO^SRXbg=Su&#|H zRU;MckKqy=;i|yc;oH|NhZ4|LW|s|BMUMSF2a*{betg)|;bI)_L0ui@a+fx@VkXFi zZng~^S?#_L|26wKvrBv~Ji~6nzp-Zbr(_OkEbp<+?vL9ld6DH#aOS8~d`o6@wgsK7 zUR=zKZY2{Ka-MrQel|7`;iF*^UPi9FsN}GCp?^Sk8NX|C?6Wve{ZR0$1s*x~XV>k| zDin@~_RlEhZxSh_;!CH~QhbW#6xrm+gtDJBk!@d}cev&|>!Am}dkSevVUM? z)yeoi#;%0lo4aiNvI(xAzq$Q)68Lv1QQ!a?`Ypl%v>As3=t~LN?X!sE3Y)+6<9Mng zt&KS)!raV9BDp{$GMpU9Bw~mMBhV9#CddIj&O#BW4}f-~w{9efgvv-{AlV;@=#QZm z{V)X@1t%#Opdd~`f&y{^p{FT0L%}C0_!NTNqDTa`Zs~Y`B$`g^@xBbk7IoK0BK-+? zD}ZbFbU*CVA`y7|tC12>48!7il<7O@q*%)WT7(R0%K!cxhU@PuJpRYLH+;+dyKn4V z>TkWVz0tqv#->I7_8T3`{rkN)cCl!^|G^vUtNd$kG_yR-{*D`)oBf?X-Q4JZ=(-01 z?<~3r>Dh{d8bHU*y$Iw@!xj1SN zD<>=YESmq4C+yX{ZNmAP$CD}xm-SlDAqsi0y`p6yxzc0r5@)#)N|oDa&+%2nziPf6 zK3*J-;;@%mM;W<6BU?7f5!$!rOY1NTAFecba4?8f1;rBVxM)!{99C&(T}%u5Ffw1HfM%I3AbqmrBTr=i_>6ZsSsJy?a&*=Qu8+ z9L5ERP04W)E}^vQl6i&yTZaoGMGI+l1@5D&=izq8%_bRRn5w`yUJjS{F0seYirs;6 zT(K~Yd9I}H@;IIldpPzkw?@(a-%AdO9EVn~HS92F=2S2o>{T5ZaEhHL8*lS_Ug>xb zn(o5)EHpso`8|tr_@0~YlkYj$OBAo;7#-|g>qx@s=x^J*p{8n1mW}>xxRyB${C`$I z4&9vN%pJnatseiYyqWv{80keNXKq^p&|EUFaDj&X`$|y(wbm-t>Cc-<>a-==(r}$a zT2Mca6`|g$+4>LU+guS+K)+KpSQ-AW76iq(%9J2;ijZ~+1}e(8KeI?frs+Huv`)LQ zNmwR=x2psrO8(QWGL~f6-0q6Ai)=%z;>_YP(CdDPulr*;g|{v?kgLq3Xdkr!rUad< z(4lUXNIU(A=lsMF7|i!~-kxCCjEngKAFCxQ*>2rsFJ~_adCe(Sqq(@#+d-$rT)*X--8(N=lh-9WeTLXF zuJ1W`{l%7XL$Ne7j$|rnIX`P#YXei2&nfBQ6gdC%shqv-9O?P%zd;RoodOpZ8}b%q zTe-tI;^*{#Lctyca)zDq$qwdGWA6Dkf;KSz2&~>qp7)lh7ayKp(sjP(ngW;V>bRm__f)U;{lrsU<^RbtPyO;Q z)SUOfTfgq&@Y|Vp_D|J6b-v=gWos|Svr9K;mCXppLz`z5^Eat%KL5Np6A>wG!p?D( z6&IQ02x`6-YMKc>I39Ws@k`i03xVTK+0cV>86H2M5ovTQ^Nem~<({IdL?$_c0_?7< z$3xKZ+i)?;g7MIX8O8ifDjV1qY<3F2ME5b%CX@}<*;Og`6xAm($q^LzNrk6r`Ap-Q z@y0bXjT^=rH)Pv)XB#(6Ha>h_xfWVH6IwqWT2I@P!A1f>!5(GnJo(=5!|0k5nS<#Kf+{kFYpTB8}>@? z!uj?}XbC=Tb`ZiLXmelK=jL=d<<}}*+&pt$Ti_ABu84uFn`c5Agf3n$`7w1IHnm8& z%MjP0Wy|h(Uc~yZI^T5Kjm_u&EY`#M5+7&0|BpHDzD(n3wUj4VifS&RVPTd zj2%+VE?gE`Ex34zvZ}S*G-DiBST))IN1t}%yhhL1y`2hRpX)yeS2Nn&b{NcQbNg9~ zjWl-{{0)gVzh6c=B#nprTU2R{gH~5EuT96-m&_|L6$foz!<;17IB4_QdAvb_>bF`B z+T47DQk8g5y$jb(U`Gj_Z#Y8z(swGf*=KqT%zxX?FOX( zV~P>1al}E0>tCcRU!q_g1z(}ypCM?i*S|_}o06n?S^rH6E>b}H7IUR8xy8cQQKB`( z?I`~RmHw9$FlvxBm*z?R4GMNs@DK%z_9L=Sf0KgWqF^5d{|bQ-Lg1k8IBCasmo-T> zHqh93fd*Po_gp9SDO2V45X12E2t3zP`-#jOLQsUv3lf^obh zm4koX*pg3?JjN)J19=ol=Ot|LilD$w0W^kfxxnMJ_M2%iOt8+bk|l$g8Xy#Ma5XHo#W;%qF=4@cxM{s_0BYwd}nNWuc>5SAt}&Gf9*depj_fTi$?Nsux;|6W1wk+J>pidj;fgLwmHCz8pt!aD@3_B(q8TfIEXx0$^ z>Qi-0OL*lN!=JdGkwu$2z2T5c3r^z125h>uo9I^3@^GbFg>KU!s2mSt7tXZpOWC4Y zpJW?dSSCm2nXAc>@RP%d(_D}|R=`0Ib78{?(bv%uC#KA4K1Q7i!gQ?3two%yrSY@r z)?9h&+)joUZ)V{Z7H%8MgOq_zv*aBXZrnf+EJ4beIWI|UsS6a|GFUHX7Qz>E*bRq&SRUq>aaO|FDJ8~&Yi6Gq+lP>d1w zHl1&i_`ij#GNStt4(4)EL04Da+19+X?mXCcd*0bjgU}fVn;`8+!g&SeYsGq8HsZ9$ z3l||2=M`ovb%J<>zc0=!l!Anp0vFtHVTA)MCE&LJE49-ID>cu6m0nm;m>DUkm!Yy7 z$3q(l{@lZY0t|GcfGm z7DT2(@TZJRhsHyPSmL3{&_@w@2XHmAeDob4*1a>x!G6+CA! z2S!R)rr0g*c-vk(PQIK)o(sn5vBHS>@0%ncp^c_fBUFyol6$?%b%&^^~?fEXwsrcX1#8 zI`^^2dnfAi-iZbmE^6-YguqKBs*$Kf&P55{$&Fuf)m?(W8H;zCMR%!&beA6Yj?PyT zaW#9Z);Qj(Mf2j5=Jr+zzC!Rv03q`mLY0cVQd<)z)&_?@Q^_hNP zbH<}TcGYeAU*Qmwmx!?kpS;J>x9;l%pHtQzPfA>JCHTHOk8p`myl11o7`)@1e-5#Z z%=0-!IlRTtX_96w_qX2Wte2I{Yjf5uCG!f-y0apWTdvcVYY(_+TFrglbJMgk=RKim z^>Ki06el~Qah^=kHtN6lODK>a+B^bhaH@C)_)z1(qE$>NCD%U2rU|G;j#&J z5Jt4{i^VKzNhK>|^r-LG1s;|Zsle+oanBK(!5&-k*x3}CkCI(#zaArfFSyGDaxj)( z)>7xvX$j`D`p{T zwW_soUd%U4ZMc`22p`vBW60j1JXdLUmd%4RSzIn)KTXa@&B!TM3ruY+-63#e@7Emo za`p~jB``&AZzGn+T$@ZeZ*y1otol}sOy7!)R)l?-P>sy3FQ#XepO4Ioj0B}JT2=?4Xck?8de{hQRp|Re2iFT^Grmfv#!Wh11h<>dH@x2Q4KKb6ys6+B z=Q%E-9L8nwJwsUT>Nk8swZ2<7Or@7N8^=Lhxxm>%;ZW~-M+Th2MsV3J+X(Wxe1qdX zXt@jTf#C7y`5uUJcn@0ckM{uMxUp~?p{=sXB~`W&tUFl_&6B!tJ<~yH2sc=LIrNh` zuG|gKK-ow&OK?6(#cul*yru+^DV8)NrFi+oS2k-0`a> zYhAbgL)$~fuj+u|H>Bzrg4H^=aTxZ!-R5p|5hh*g{xm{rwQvw-Vd}su2acr*{9M$Sk z^?#3N*%ut0?V@6fo88$0;TNH~mlO^!E?|1Dg$cGwna~pf^ba zym?;8mHr>-u3ISRrr^5>a=tX6Ihc?pVzCrQH<>PvLyMgB{9clI3Q@TR`2!5B;yo0xK1^GCoH=oP%0+v;Sc%sNz#4=O zfUG$VSgNYoG7*$?#~U8_!UN|k-fdWa@x|{{yfZM>aP)lTH2_%Wvdds67DhN8f+1I! z|0b32`7`27M5MF{JI7U4Tx3Go?6xP~IsVR}Y`0mRxP}OFUkfdI?NhIODyyu%SkD5O z^35pbZ&F!({!`*iLsNTpK4LK6^Y1)8Reuy_luOx80!y7uw3{PtWi<*3`AxjAROFE(c=UVU6E0Sa z8bj=k=GhE#O%UBI4+*2Y3#N?YrYWQ8P%EgvM03KYDfkQpFH`VY z3NBIb4GR7p1%FP#Bn7`p!9Swla|mSTRwO;rDIiZ!1`&b6xz%>^ZPk8bXETD$i~LV{ zZ*(k!U#p+)?Dn@`_aI;(AuHUEa^gTm?6^s_mGWGbu7HQCO3e=`SVgXhXb3JKI*LV; z$cKI-{#{f!wvv$-Tt8H$6_sD)GBFStd48$LnHJMeS{rb~D&2fiZRKtD0K@QVWwIBm zlDYGIUTNidWzh=fp$XdtcCY->{*tAIj!0okwaSvEg?`RT4^W+|LuCicQ)PtPtnwVM zCy!A(%oeB0p(WxXBTxBYj|lHuOulX-GT&o70D7ArZZ@kxAsZPY)0BtEk~IKH2HD|m zv1*NtWO-`e$yLWCEbI*|F2zJ->~c-kJ?;O{%NOOq@sXFOQu)PLf;%jgS6Hb!>=Y^) zdRJ&`0&Ng}NHx3YP@&agR3%Ei0o1wA)VvZpHdc+`+S}UChOvrmZD6Wi?XmnRlQSqNVmE$kCG4DhYI=yKwEfV%`W0;*|t&BzuywnXHKQ-a{FY@9*s7(YUJt_Y!|bjOBYw z*9ibopyBmH(^U`5C~L=+wNuLaYgP663GbN+7b~Z$U>Vdsu5`oS)Hd#K>iROPg1pNC zx*VwTUti*S*+-XsRsMenda6G1mv!pJC2wz?s(bXj|2=XcrH+Txtg`MR%!Uxak)P=UG{0Ie zK}`+8c&K$oF@KXvYc>QeE<1(CX(Y|G38mFKyDH_LqN+qDIf5%34mpnR=}4 zDd?jhiNGKL%W{{<*S2M#KcLNQwLc=YKesXOjpu8cYtH|Jci4CVb3j)nQU$X2Yb>Mv;usz3n0G$_8g93;O<6K!W9m&dgY0F ztF%9*yqjmES z*!D)vCY3sB?K;jZ3uD8n^f||}@Dc`V9?OEt>)yQ^jYe`(f=yx_Ijs=l% zDS}_+)VoW*<+L0w0ohEndw?y6%^$>a_}bZ5&cZP5LJ-0EvloJsjnx1* z6&S0fMH(5anP)Iov+UMjt9BvC^B~GxXj2_z!nQ~hbwqIY!gW>f6<L<|p$u?q29W8eAwOV+G)94r~>uH8}$dj3TVUfWbJa z==Pt&wv8|y*NqIx4`yEim^y2;k9dOBqZtxftX9{nyH2Ht6A!ED=#b&y5!h;T<;^Q^ zTzQKNXv`IWr(YgUK8MNm`fvXFD{gAFTw=r4gfYdsUC~`Fk7Nk(;`id7h#$wf{saZR z6g-81!}`oEnBXS(7RzRLMC|T}#7EdR8#yKdbIhQ{vv<%$`lq-TO{CRqB3YbWsU!U+csdwz_AsMEk(%_xx63nye~Rm1_x!o(IkJzT8%+9Q{`rd!%CMW{2 z@INW|TMCMUx&M_eQ$Xmcu?i6e=KdF7+YMi>f6WcG2D?M9sKa)UYwP`8*F6Y$JE%v_562c9KA^n85W z^YI^gHbP2NrhKmD(=9XJ=5cTHHE$(9Svu}r`qQ%GJ}>sor0{0Br)(Joi?91wYFe4C w;4@`js4Y?`7i6iA`e!TH=>_JFKrTjU1Kzw+KP`LA>%|yS$Xa!SC9r<@|5WSn!2kdN literal 0 HcmV?d00001 diff --git a/quiz/tests/conftest.py b/quiz/tests/conftest.py new file mode 100644 index 0000000..33b5b37 --- /dev/null +++ b/quiz/tests/conftest.py @@ -0,0 +1,108 @@ +import pytest +from django.conf import settings + + +@pytest.fixture(scope='session') +def django_db_setup(): + """Configure test database""" + settings.DATABASES['default'] = { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + 'ATOMIC_REQUESTS': False, + 'AUTOCOMMIT': True, + 'CONN_MAX_AGE': 0, + 'OPTIONS': {}, + 'TIME_ZONE': None, + 'USER': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + 'TEST': { + 'NAME': None, + }, + } + + +@pytest.fixture +def sample_mcq_content(): + """Fixture for standard MCQ content""" + return """--- +tags: [ah2, provfrĂ„ga, frĂ„getyp/mcq, cerebrum] +date: 2022-01-15 +--- +Vilka av följande rĂ€knas till storhjĂ€rnans basala kĂ€rnor? + +**VĂ€lj tvĂ„ alternativ** +- A: Putamen +- B: Nucleus Ruber +- C: Substantia nigra +- D: Nucleus caudatus + +```spoiler-block: +A och D +``` +""" + + +@pytest.fixture +def sample_scq_content(): + """Fixture for standard SCQ content""" + return """--- +tags: [ah2, provfrĂ„ga, frĂ„getyp/scq, anatomi] +date: 2022-01-15 +--- +What is the correct answer? + +**VĂ€lj ett alternativ:** +- A: Wrong answer +- B: Correct answer +- C: Another wrong + +```spoiler-block: +B +``` +""" + + +@pytest.fixture +def sample_textalternativ_content(): + """Fixture for text alternative question""" + return """--- +tags: [frĂ„getyp/textalternativ, öga, anatomi] +--- +Svara pĂ„ följande frĂ„gor: + +a) Bokstaven B sitter i en lob, vilken? +- Lobus temporalis +- Lobus frontalis +- Lobus parietalis + +b) Vilket funktionellt centra Ă„terfinns dĂ€r? +- Syncentrum +- Motorcentrum +- Somatosensoriskt centrum + +```spoiler-block: +a) Lobus parietalis +b) Somatosensoriskt centrum +``` +""" + + +@pytest.fixture +def sample_textfalt_content(): + """Fixture for text field question""" + return """--- +tags: [frĂ„getyp/textfĂ€lt, öga] +--- +**Fyll i rĂ€tt siffra!** + +a) Vilken siffra pekar pĂ„ gula flĂ€cken? +b) Vilken siffra pekar pĂ„ choroidea? + +```spoiler-block: +a) 7 +b) 6 +``` +""" + diff --git a/quiz/tests/test_admin.py b/quiz/tests/test_admin.py new file mode 100644 index 0000000..5954951 --- /dev/null +++ b/quiz/tests/test_admin.py @@ -0,0 +1,184 @@ +import pytest +from django.contrib.auth.models import User +from django.urls import reverse +from quiz.models import QuizUser, Question, Option, QuizResult + + +@pytest.mark.django_db +@pytest.mark.admin +class TestAdminPages: + """Test that all admin pages render without errors""" + + @pytest.fixture + def admin_client(self, client, django_user_model, db): + """Create authenticated admin client""" + admin_user = django_user_model.objects.create_superuser( + username='testadmin', + email='admin@test.com', + password='admin123' + ) + client.login(username='testadmin', password='admin123') + return client + + @pytest.fixture + def test_data(self, db): + """Create test data""" + quiz_user = QuizUser.objects.create(session_key='test_session_123') + + question = Question.objects.create( + file_path='test/question1.md', + text='Test question?', + correct_answer='A', + file_mtime=1234567890.0 + ) + + Option.objects.create(question=question, letter='A', text='Correct answer') + Option.objects.create(question=question, letter='B', text='Wrong answer') + + quiz_result = QuizResult.objects.create( + user=quiz_user, + question=question, + selected_answer='A', + is_correct=True + ) + + return { + 'quiz_user': quiz_user, + 'question': question, + 'quiz_result': quiz_result + } + + def test_admin_index(self, admin_client): + """Test admin index page""" + response = admin_client.get(reverse('admin:index')) + assert response.status_code == 200 + assert 'Site administration' in response.content.decode() + + def test_question_changelist(self, admin_client, test_data): + """Test Question list page""" + response = admin_client.get(reverse('admin:quiz_question_changelist')) + assert response.status_code == 200 + assert 'Test question?' in response.content.decode() + + def test_question_add(self, admin_client): + """Test Question add page""" + response = admin_client.get(reverse('admin:quiz_question_add')) + assert response.status_code == 200 + assert 'Add question' in response.content.decode() + + def test_question_change(self, admin_client, test_data): + """Test Question change/edit page""" + response = admin_client.get( + reverse('admin:quiz_question_change', args=[test_data['question'].pk]) + ) + assert response.status_code == 200 + assert 'Test question?' in response.content.decode() + assert 'Correct answer' in response.content.decode() + + def test_question_delete(self, admin_client, test_data): + """Test Question delete page""" + response = admin_client.get( + reverse('admin:quiz_question_delete', args=[test_data['question'].pk]) + ) + assert response.status_code == 200 + assert 'Are you sure' in response.content.decode() + + def test_option_add(self, admin_client): + """Test Option add page""" + response = admin_client.get(reverse('admin:quiz_option_add')) + assert response.status_code == 200 + assert 'Add option' in response.content.decode() + + def test_option_change(self, admin_client, test_data): + """Test Option change/edit page""" + option = test_data['question'].options.first() + response = admin_client.get( + reverse('admin:quiz_option_change', args=[option.pk]) + ) + assert response.status_code == 200 + assert 'Correct answer' in response.content.decode() + + def test_quizuser_changelist(self, admin_client, test_data): + """Test QuizUser list page""" + response = admin_client.get(reverse('admin:quiz_quizuser_changelist')) + assert response.status_code == 200 + assert 'test_session' in response.content.decode() + + def test_quizuser_add(self, admin_client): + """Test QuizUser add page""" + response = admin_client.get(reverse('admin:quiz_quizuser_add')) + assert response.status_code == 200 + assert 'Add Quiz User' in response.content.decode() + + def test_quizuser_change(self, admin_client, test_data): + """Test QuizUser change/edit page""" + response = admin_client.get( + reverse('admin:quiz_quizuser_change', args=[test_data['quiz_user'].pk]) + ) + assert response.status_code == 200 + assert 'test_session' in response.content.decode() + + def test_quizresult_changelist(self, admin_client, test_data): + """Test QuizResult list page""" + response = admin_client.get(reverse('admin:quiz_quizresult_changelist')) + assert response.status_code == 200 + assert 'Test question' in response.content.decode() + + def test_quizresult_add(self, admin_client): + """Test QuizResult add page""" + response = admin_client.get(reverse('admin:quiz_quizresult_add')) + assert response.status_code == 200 + assert 'Add quiz result' in response.content.decode() + + def test_quizresult_change(self, admin_client, test_data): + """Test QuizResult change/edit page""" + response = admin_client.get( + reverse('admin:quiz_quizresult_change', args=[test_data['quiz_result'].pk]) + ) + assert response.status_code == 200 + assert 'Test question' in response.content.decode() + + def test_admin_custom_displays(self, admin_client, test_data): + """Test custom admin display methods render correctly""" + # Question admin with custom fields + response = admin_client.get(reverse('admin:quiz_question_changelist')) + assert 'question1.md' in response.content.decode() + + # QuizUser admin with score percentage + response = admin_client.get(reverse('admin:quiz_quizuser_changelist')) + assert '100.0%' in response.content.decode() + + # QuizResult admin with result status + response = admin_client.get(reverse('admin:quiz_quizresult_changelist')) + assert 'Correct' in response.content.decode() + + def test_admin_search(self, admin_client, test_data): + """Test admin search functionality""" + response = admin_client.get( + reverse('admin:quiz_question_changelist') + '?q=Test' + ) + assert response.status_code == 200 + assert 'Test question?' in response.content.decode() + + def test_admin_filters(self, admin_client, test_data): + """Test admin filter functionality""" + response = admin_client.get( + reverse('admin:quiz_quizresult_changelist') + '?is_correct__exact=1' + ) + assert response.status_code == 200 + + @pytest.mark.parametrize('url_name', [ + 'admin:index', + 'admin:quiz_question_changelist', + 'admin:quiz_question_add', + 'admin:quiz_quizuser_changelist', + 'admin:quiz_quizuser_add', + 'admin:quiz_quizresult_changelist', + 'admin:quiz_quizresult_add', + ]) + def test_all_admin_pages_no_errors(self, admin_client, test_data, url_name): + """Integration test: verify no admin pages return errors""" + url = reverse(url_name) + response = admin_client.get(url) + assert response.status_code == 200, f"Failed to load {url}" + diff --git a/quiz/tests/test_import.py b/quiz/tests/test_import.py new file mode 100644 index 0000000..5302915 --- /dev/null +++ b/quiz/tests/test_import.py @@ -0,0 +1,576 @@ +import pytest +from pathlib import Path +from quiz.utils.importer import parse_markdown_question, import_question_file, ImportStats +from quiz.models import Question, Option + + +@pytest.mark.django_db +@pytest.mark.import_tests +class TestMarkdownParsing: + """Test parsing of various Obsidian markdown question formats""" + + def test_parse_single_choice_question(self): + """Test parsing standard single choice question (SCQ)""" + content = """--- +tags: [ah2, provfrĂ„ga, frĂ„getyp/scq, anatomi] +date: 2022-01-15 +--- +What is the correct answer? + +**VĂ€lj ett alternativ:** +- A: Wrong answer +- B: Correct answer +- C: Another wrong + +```spoiler-block: +B +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert data['text'] == 'What is the correct answer?' + assert data['correct_answer'] == 'B' + assert data['has_answer'] is True + assert data['question_type'] == 'scq' + assert len(data['options']) == 3 + assert data['options'][0] == ('A', 'Wrong answer') + assert data['options'][1] == ('B', 'Correct answer') + + def test_parse_multiple_choice_question(self): + """Test parsing multiple choice question (MCQ) with 'och' separator""" + content = """--- +tags: [ah2, provfrĂ„ga, frĂ„getyp/mcq, cerebrum] +date: 2022-01-15 +--- +Vilka av följande rĂ€knas till storhjĂ€rnans basala kĂ€rnor? + +**VĂ€lj tvĂ„ alternativ** +- A: Putamen +- B: Nucleus Ruber +- C: Substantia nigra +- D: Nucleus caudatus + +```spoiler-block: +A och D +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert 'Vilka av följande' in data['text'] + assert data['correct_answer'] == 'A,D' # Normalized to comma-separated + assert data['has_answer'] is True + assert data['question_type'] == 'mcq' + assert len(data['options']) == 4 + + def test_parse_multiple_choice_comma_separated(self): + """Test MCQ with comma-separated answer""" + content = """--- +tags: [frĂ„getyp/mcq] +--- +Select two options: + +- A: Option A +- B: Option B +- C: Option C +- D: Option D + +```spoiler-block: +B, C +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert data['correct_answer'] == 'B,C' + assert data['has_answer'] is True + + def test_parse_options_without_colon(self): + """Test parsing options in format '- A' without text""" + content = """--- +tags: [frĂ„getyp/scq] +--- +Which letter? + +**VĂ€lj ett alternativ:** +- A +- B +- C +- D + +```spoiler-block: +C +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert len(data['options']) == 4 + assert all(text == '' for _, text in data['options']) + assert data['correct_answer'] == 'C' + + def test_parse_textalternativ_question(self): + """Test text alternative question type""" + content = """--- +tags: [frĂ„getyp/textalternativ, öga, anatomi] +--- +Svara pĂ„ följande frĂ„gor: + +a) Bokstaven B sitter i en lob, vilken? +- Lobus temporalis +- Lobus frontalis +- Lobus parietalis + +b) Vilket funktionellt centra Ă„terfinns dĂ€r? +- Syncentrum +- Motorcentrum +- Somatosensoriskt centrum + +```spoiler-block: +a) Lobus parietalis +b) Somatosensoriskt centrum +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert data['question_type'] == 'textalternativ' + assert data['has_answer'] is True + assert 'Lobus parietalis' in data['correct_answer'] + assert 'Somatosensoriskt centrum' in data['correct_answer'] + + def test_parse_textfalt_question(self): + """Test text field (fill-in) question type""" + content = """--- +tags: [frĂ„getyp/textfĂ€lt, öga] +--- +**Fyll i rĂ€tt siffra!** + +a) Vilken siffra pekar pĂ„ gula flĂ€cken? +b) Vilken siffra pekar pĂ„ choroidea? + +```spoiler-block: +a) 7 +b) 6 +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert data['question_type'] == 'textfĂ€lt' + assert data['has_answer'] is True + assert '7' in data['correct_answer'] + assert '6' in data['correct_answer'] + + def test_skip_todo_answers(self): + """Test that questions with TODO are skipped""" + content = """--- +tags: [frĂ„getyp/mcq] +--- +What is this? + +- A: Option A +- B: Option B + +```spoiler-block: +TODO +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert data['has_answer'] is False + + def test_skip_non_question_files(self): + """Test that files without question tags are skipped""" + content = """--- +tags: [ah2, notes, general] +--- +This is just a note, not a question. +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is False + + def test_parse_with_images(self): + """Test parsing questions with embedded images""" + content = """--- +tags: [frĂ„getyp/scq, bild] +--- +![[image.png|338x258]] +Vilken bokstav pĂ„ denna bild sitter pĂ„ Mesencephalon? + +**VĂ€lj ett alternativ:** +- A +- B +- C +- D +- E +- F + +```spoiler-block: +F +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert 'Vilken bokstav' in data['text'] + assert data['correct_answer'] == 'F' + assert len(data['options']) == 6 + + def test_parse_yaml_list_format_tags(self): + """Test parsing tags in YAML list format""" + content = """--- +tags: + - ah2 + - provfrĂ„ga + - frĂ„getyp/scq + - anatomi +date: 2022-01-15 +--- +Question text? + +- A: Answer A +- B: Answer B + +```spoiler-block: +A +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert data['question_type'] == 'scq' + + def test_parse_mixed_option_formats(self): + """Test parsing with inconsistent option formatting""" + content = """--- +tags: [frĂ„getyp/mcq] +--- +Select correct options: + +**VĂ€lj tvĂ„ alternativ:** +- A: First option with text +- B:Second option no space +- C: Third option extra spaces +- D:Fourth with trailing + +```spoiler-block: +A och C +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert len(data['options']) == 4 + assert data['options'][0] == ('A', 'First option with text') + assert data['options'][1] == ('B', 'Second option no space') + assert data['correct_answer'] == 'A,C' + + def test_parse_question_with_multiple_paragraphs(self): + """Test question text extraction with multiple paragraphs""" + content = """--- +tags: [frĂ„getyp/scq] +--- +This is a longer question that spans multiple lines +and has additional context. + +**VĂ€lj ett alternativ:** +- A: Answer +- B: Another + +```spoiler-block: +A +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert 'This is a longer question' in data['text'] + + +@pytest.mark.django_db +@pytest.mark.import_tests +class TestQuestionImport: + """Test actual import of questions to database""" + + def test_import_single_question(self, tmp_path): + """Test importing a single question file""" + question_file = tmp_path / "question1.md" + question_file.write_text("""--- +tags: [frĂ„getyp/scq] +--- +Test question? + +- A: Correct +- B: Wrong + +```spoiler-block: +A +``` +""") + + stats = ImportStats() + result = import_question_file(question_file, tmp_path, stats, force=True) + + assert result in ['imported', 'updated'] + assert stats.questions_with_answers == 1 + assert stats.mcq_questions == 1 + + # Verify in database + question = Question.objects.get(text='Test question?') + assert question.correct_answer == 'A' + assert question.options.count() == 2 + + def test_import_multi_select_question(self, tmp_path): + """Test importing multi-select question""" + question_file = tmp_path / "question2.md" + question_file.write_text("""--- +tags: [frĂ„getyp/mcq] +--- +Multi-select question? + +- A: First correct +- B: Wrong +- C: Second correct + +```spoiler-block: +A och C +``` +""") + + stats = ImportStats() + import_question_file(question_file, tmp_path, stats, force=True) + + question = Question.objects.get(text='Multi-select question?') + assert question.correct_answer == 'A,C' + assert question.options.count() == 3 + + def test_skip_question_without_answer(self, tmp_path): + """Test that questions with TODO are not imported""" + question_file = tmp_path / "question3.md" + question_file.write_text("""--- +tags: [frĂ„getyp/scq] +--- +Incomplete question? + +- A: Option A +- B: Option B + +```spoiler-block: +TODO +``` +""") + + stats = ImportStats() + result = import_question_file(question_file, tmp_path, stats, force=True) + + assert result == 'skipped_todo' + assert stats.questions_with_todo == 1 + assert Question.objects.filter(text='Incomplete question?').count() == 0 + + def test_mtime_tracking(self, tmp_path): + """Test that file modification time is tracked""" + question_file = tmp_path / "question4.md" + question_file.write_text("""--- +tags: [frĂ„getyp/scq] +--- +What is the correct answer? + +**VĂ€lj ett alternativ:** +- A: Answer A +- B: Answer B + +```spoiler-block: +A +``` +""") + + stats = ImportStats() + result = import_question_file(question_file, tmp_path, stats, force=True) + + # Verify import succeeded + assert result in ['imported', 'updated'], f"Import failed with status: {result}" + assert stats.created == 1, f"Expected 1 created, got {stats.created}" + + question = Question.objects.get(text='What is the correct answer?') + assert question.file_mtime is not None + assert question.file_mtime == question_file.stat().st_mtime + + def test_update_existing_question(self, tmp_path): + """Test updating an existing question""" + question_file = tmp_path / "question5.md" + + # Initial import + question_file.write_text("""--- +tags: [frĂ„getyp/scq] +--- +What is the original question here? + +**VĂ€lj ett alternativ:** +- A: First answer +- B: Second answer + +```spoiler-block: +A +``` +""") + + stats1 = ImportStats() + result1 = import_question_file(question_file, tmp_path, stats1, force=True) + assert result1 in ['imported', 'updated'], f"Initial import failed: {result1}" + assert stats1.created == 1 + + # Update the file + import time + time.sleep(0.1) # Ensure mtime changes + question_file.write_text("""--- +tags: [frĂ„getyp/scq] +--- +What is the original question here? + +**VĂ€lj ett alternativ:** +- A: First answer +- B: Second answer +- C: Third option + +```spoiler-block: +C +``` +""") + + stats2 = ImportStats() + result = import_question_file(question_file, tmp_path, stats2, force=False) + + assert result == 'updated' + assert stats2.updated == 1 + + # Verify update + question = Question.objects.get(text='What is the original question here?') + assert question.correct_answer == 'C' + assert question.options.count() == 3 + + +@pytest.mark.django_db +@pytest.mark.import_tests +class TestImportStatistics: + """Test import statistics tracking""" + + def test_statistics_aggregation(self, tmp_path): + """Test that statistics are correctly aggregated""" + # Create multiple question files + (tmp_path / "folder1").mkdir() + (tmp_path / "folder2").mkdir() + + (tmp_path / "folder1" / "q1.md").write_text("""--- +tags: [frĂ„getyp/mcq] +--- +Question number one? + +**VĂ€lj tvĂ„ alternativ:** +- A: Answer A +- B: Answer B +```spoiler-block: +A +``` +""") + + (tmp_path / "folder1" / "q2.md").write_text("""--- +tags: [frĂ„getyp/scq] +--- +Question number two? + +**VĂ€lj ett alternativ:** +- A: Answer A +```spoiler-block: +TODO +``` +""") + + (tmp_path / "folder2" / "q3.md").write_text("""--- +tags: [notes] +--- +Not a question, just notes +""") + + from quiz.utils.importer import import_questions + stats = import_questions(tmp_path, tmp_path, force=True) + + assert stats.total_files == 3 + assert stats.mcq_questions == 2 + assert stats.questions_with_answers == 1 + assert stats.questions_with_todo == 1 + assert stats.non_mcq_skipped == 1 + + +@pytest.mark.django_db +class TestEdgeCases: + """Test edge cases and error handling""" + + def test_malformed_frontmatter(self): + """Test handling of malformed frontmatter""" + content = """--- +tags: [frĂ„getyp/scq] +date: broken +--- +Question? +- A: Answer +```spoiler-block: +A +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + # Should still parse as question if tags are recognizable + assert is_question is True + + def test_missing_spoiler_block(self): + """Test question without spoiler block""" + content = """--- +tags: [frĂ„getyp/scq] +--- +Question without answer? + +- A: Option A +- B: Option B +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert data['has_answer'] is False + + def test_empty_spoiler_block(self): + """Test question with empty spoiler block""" + content = """--- +tags: [frĂ„getyp/scq] +--- +Question with empty answer block? + +**VĂ€lj ett alternativ:** +- A: Option A + +```spoiler-block: +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert is_question is True + assert data.get('has_answer') is False + + def test_special_characters_in_text(self): + """Test handling of special characters""" + content = """--- +tags: [frĂ„getyp/scq] +--- +What about "quotes" & tags? + +- A: Option with ÄÀö +- B: Option with Ă©mojis 🎉 + +```spoiler-block: +A +``` +""" + is_question, data = parse_markdown_question(Path("test.md"), content) + + assert '"quotes"' in data['text'] + assert 'ÄÀö' in data['options'][0][1] +