From b09eb2c7d6f15a31aba21e42da22439a0c3982b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhn-Vitus=20Sa=C3=9F?= Date: Sat, 8 Mar 2025 15:34:39 +0100 Subject: [PATCH] Fix #5 --- core/homepage/static/homepage/t-style.css | 519 ++++++++++++++++++ core/library/templates/library/base.html | 1 + .../library/delete_confirmation.html | 42 +- .../templates/library/detail_quiz.html | 106 +++- .../question/question_multiple_choice.html | 164 ++++++ .../library/question/question_true_false.html | 59 ++ core/library/views.py | 192 ++++++- 7 files changed, 1046 insertions(+), 37 deletions(-) create mode 100644 core/library/templates/library/question/question_multiple_choice.html create mode 100644 core/library/templates/library/question/question_true_false.html diff --git a/core/homepage/static/homepage/t-style.css b/core/homepage/static/homepage/t-style.css index 2a8c3dd..22eaf3b 100644 --- a/core/homepage/static/homepage/t-style.css +++ b/core/homepage/static/homepage/t-style.css @@ -6,26 +6,45 @@ 'Noto Color Emoji'; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); --color-red-500: oklch(0.637 0.237 25.331); --color-red-600: oklch(0.577 0.245 27.325); + --color-red-700: oklch(0.505 0.213 27.518); + --color-red-800: oklch(0.444 0.177 26.899); --color-yellow-500: oklch(0.795 0.184 86.047); --color-green-500: oklch(0.723 0.219 149.579); + --color-green-600: oklch(0.627 0.194 149.214); + --color-green-700: oklch(0.527 0.154 150.069); --color-blue-100: oklch(0.932 0.032 255.585); --color-blue-400: oklch(0.707 0.165 254.624); --color-blue-500: oklch(0.623 0.214 259.815); --color-blue-600: oklch(0.546 0.245 262.881); + --color-blue-700: oklch(0.488 0.243 264.376); + --color-blue-800: oklch(0.424 0.199 265.638); --color-indigo-500: oklch(0.585 0.233 277.117); --color-indigo-600: oklch(0.511 0.262 276.966); + --color-purple-100: oklch(0.946 0.033 307.174); --color-purple-500: oklch(0.627 0.265 303.9); + --color-purple-800: oklch(0.438 0.218 303.724); + --color-gray-50: oklch(0.985 0.002 247.839); + --color-gray-100: oklch(0.967 0.003 264.542); --color-gray-200: oklch(0.928 0.006 264.531); + --color-gray-300: oklch(0.872 0.01 258.338); + --color-gray-400: oklch(0.707 0.022 261.325); --color-gray-500: oklch(0.551 0.027 264.364); --color-gray-600: oklch(0.446 0.03 256.802); + --color-gray-700: oklch(0.373 0.034 259.733); --color-gray-900: oklch(0.21 0.034 264.665); --color-stone-900: oklch(0.216 0.006 56.043); --color-white: #fff; --spacing: 0.25rem; --breakpoint-lg: 64rem; --container-md: 28rem; + --container-lg: 32rem; + --container-7xl: 80rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); --text-sm: 0.875rem; --text-sm--line-height: calc(1.25 / 0.875); --text-lg: 1.125rem; @@ -40,6 +59,7 @@ --text-8xl--line-height: 1; --font-weight-extralight: 200; --font-weight-light: 300; + --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --font-weight-extrabold: 800; @@ -200,12 +220,21 @@ } } @layer utilities { + .collapse { + visibility: collapse; + } .absolute { position: absolute; } + .relative { + position: relative; + } .static { position: static; } + .sticky { + position: sticky; + } .top-0 { top: calc(var(--spacing) * 0); } @@ -224,6 +253,24 @@ .z-40 { z-index: 40; } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } .m-0 { margin: calc(var(--spacing) * 0); } @@ -251,18 +298,54 @@ .mt-4 { margin-top: calc(var(--spacing) * 4); } + .mt-6 { + margin-top: calc(var(--spacing) * 6); + } .mt-8 { margin-top: calc(var(--spacing) * 8); } + .mt-10 { + margin-top: calc(var(--spacing) * 10); + } + .mr-0 { + margin-right: calc(var(--spacing) * 0); + } .mr-0\.75 { margin-right: calc(var(--spacing) * 0.75); } + .mr-1\.5 { + margin-right: calc(var(--spacing) * 1.5); + } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } + .mb-3 { + margin-bottom: calc(var(--spacing) * 3); + } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } .mb-6 { margin-bottom: calc(var(--spacing) * 6); } + .mb-8 { + margin-bottom: calc(var(--spacing) * 8); + } + .ml-0 { + margin-left: calc(var(--spacing) * 0); + } .ml-0\.75 { margin-left: calc(var(--spacing) * 0.75); } + .ml-1 { + margin-left: calc(var(--spacing) * 1); + } + .ml-2 { + margin-left: calc(var(--spacing) * 2); + } + .ml-4 { + margin-left: calc(var(--spacing) * 4); + } .block { display: block; } @@ -275,6 +358,21 @@ .hidden { display: none; } + .inline-flex { + display: inline-flex; + } + .list-item { + display: list-item; + } + .table { + display: table; + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-5 { + height: calc(var(--spacing) * 5); + } .h-12 { height: calc(var(--spacing) * 12); } @@ -290,12 +388,51 @@ .h-full { height: 100%; } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-12 { + width: calc(var(--spacing) * 12); + } .w-full { width: 100%; } + .max-w-7xl { + max-width: var(--container-7xl); + } + .max-w-lg { + max-width: var(--container-lg); + } .max-w-screen-lg { max-width: var(--breakpoint-lg); } + .flex-shrink-0 { + flex-shrink: 0; + } + .flex-grow { + flex-grow: 1; + } + .border-collapse { + border-collapse: collapse; + } + .transform { + transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y); + } + .cursor-pointer { + cursor: pointer; + } + .resize { + resize: both; + } + .list-inside { + list-style-position: inside; + } + .list-disc { + list-style-type: disc; + } .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } @@ -311,6 +448,9 @@ .items-center { align-items: center; } + .items-start { + align-items: flex-start; + } .justify-around { justify-content: space-around; } @@ -329,6 +469,20 @@ .gap-4 { gap: calc(var(--spacing) * 4); } + .space-y-1 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-3 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse))); + } + } .space-y-4 { :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; @@ -336,6 +490,13 @@ margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); } } + .space-y-6 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); + } + } .space-x-2 { :where(& > :not(:last-child)) { --tw-space-x-reverse: 0; @@ -343,6 +504,13 @@ margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))); } } + .space-x-3 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse))); + } + } .space-x-4 { :where(& > :not(:last-child)) { --tw-space-x-reverse: 0; @@ -350,12 +518,29 @@ margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); } } + .divide-y { + :where(& > :not(:last-child)) { + --tw-divide-y-reverse: 0; + border-bottom-style: var(--tw-border-style); + border-top-style: var(--tw-border-style); + border-top-width: calc(1px * var(--tw-divide-y-reverse)); + border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + } + } + .divide-gray-200 { + :where(& > :not(:last-child)) { + border-color: var(--color-gray-200); + } + } .overflow-hidden { overflow: hidden; } .overflow-x-auto { overflow-x: auto; } + .rounded { + border-radius: 0.25rem; + } .rounded-full { border-radius: calc(infinity * 1px); } @@ -371,6 +556,10 @@ .rounded-xl { border-radius: var(--radius-xl); } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } .border-2 { border-style: var(--tw-border-style); border-width: 2px; @@ -383,15 +572,28 @@ border-style: var(--tw-border-style); border-width: 4px; } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } .border-blue-100 { border-color: var(--color-blue-100); } .border-blue-600 { border-color: var(--color-blue-600); } + .border-gray-200 { + border-color: var(--color-gray-200); + } + .border-gray-300 { + border-color: var(--color-gray-300); + } .border-gray-500 { border-color: var(--color-gray-500); } + .border-transparent { + border-color: transparent; + } .bg-blue-100 { background-color: var(--color-blue-100); } @@ -404,6 +606,12 @@ .bg-blue-600 { background-color: var(--color-blue-600); } + .bg-gray-50 { + background-color: var(--color-gray-50); + } + .bg-gray-100 { + background-color: var(--color-gray-100); + } .bg-gray-200 { background-color: var(--color-gray-200); } @@ -416,9 +624,15 @@ .bg-indigo-500 { background-color: var(--color-indigo-500); } + .bg-purple-100 { + background-color: var(--color-purple-100); + } .bg-purple-500 { background-color: var(--color-purple-500); } + .bg-red-100 { + background-color: var(--color-red-100); + } .bg-red-500 { background-color: var(--color-red-500); } @@ -434,6 +648,9 @@ .object-cover { object-fit: cover; } + .p-0 { + padding: calc(var(--spacing) * 0); + } .p-0\.75 { padding: calc(var(--spacing) * 0.75); } @@ -446,21 +663,60 @@ .p-4 { padding: calc(var(--spacing) * 4); } + .p-6 { + padding: calc(var(--spacing) * 6); + } + .p-8 { + padding: calc(var(--spacing) * 8); + } .px-1 { padding-inline: calc(var(--spacing) * 1); } + .px-2 { + padding-inline: calc(var(--spacing) * 2); + } + .px-2\.5 { + padding-inline: calc(var(--spacing) * 2.5); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } .px-4 { padding-inline: calc(var(--spacing) * 4); } + .py-0\.5 { + padding-block: calc(var(--spacing) * 0.5); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-1\.5 { + padding-block: calc(var(--spacing) * 1.5); + } .py-2 { padding-block: calc(var(--spacing) * 2); } + .py-4 { + padding-block: calc(var(--spacing) * 4); + } + .py-5 { + padding-block: calc(var(--spacing) * 5); + } + .py-8 { + padding-block: calc(var(--spacing) * 8); + } + .py-12 { + padding-block: calc(var(--spacing) * 12); + } .pb-4 { padding-bottom: calc(var(--spacing) * 4); } .text-center { text-align: center; } + .font-mono { + font-family: var(--font-mono); + } .text-2xl { font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); @@ -481,6 +737,10 @@ font-size: var(--text-xl); line-height: var(--tw-leading, var(--text-xl--line-height)); } + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)); + } .text-\[clamp\(0\.3rem\,5vw\,1rem\)\] { font-size: clamp(0.3rem, 5vw, 1rem); } @@ -512,6 +772,10 @@ --tw-font-weight: var(--font-weight-light); font-weight: var(--font-weight-light); } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } .font-semibold { --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); @@ -519,15 +783,54 @@ .break-words { overflow-wrap: break-word; } + .break-all { + word-break: break-all; + } + .whitespace-pre-wrap { + white-space: pre-wrap; + } .text-blue-600 { color: var(--color-blue-600); } + .text-blue-800 { + color: var(--color-blue-800); + } + .text-gray-400 { + color: var(--color-gray-400); + } + .text-gray-500 { + color: var(--color-gray-500); + } .text-gray-600 { color: var(--color-gray-600); } + .text-gray-700 { + color: var(--color-gray-700); + } + .text-gray-900 { + color: var(--color-gray-900); + } + .text-green-500 { + color: var(--color-green-500); + } + .text-green-600 { + color: var(--color-green-600); + } + .text-green-700 { + color: var(--color-green-700); + } + .text-purple-800 { + color: var(--color-purple-800); + } + .text-red-500 { + color: var(--color-red-500); + } .text-red-600 { color: var(--color-red-600); } + .text-red-700 { + color: var(--color-red-700); + } .text-stone-900 { color: var(--color-stone-900); } @@ -537,6 +840,13 @@ .italic { font-style: italic; } + .underline { + text-decoration-line: underline; + } + .shadow { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } .shadow-\[0_2px_2px_rgba\(0\,0\,0\,0\.1\)\] { --tw-shadow: 0 2px 2px var(--tw-shadow-color, rgba(0,0,0,0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -545,14 +855,31 @@ --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } .filter { filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); } + .backdrop-filter { + -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + } .transition { transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); } + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } .duration-200 { --tw-duration: 200ms; transition-duration: 200ms; @@ -577,6 +904,83 @@ } } } + .hover\:bg-blue-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-600); + } + } + } + .hover\:bg-blue-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-700); + } + } + } + .hover\:bg-gray-50 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-50); + } + } + } + .hover\:bg-gray-200 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-200); + } + } + } + .hover\:bg-gray-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-600); + } + } + } + .hover\:bg-green-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-600); + } + } + } + .hover\:bg-red-200 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-200); + } + } + } + .hover\:bg-red-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-600); + } + } + } + .hover\:bg-red-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-700); + } + } + } + .hover\:text-gray-700 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-700); + } + } + } + .hover\:text-red-800 { + &:hover { + @media (hover: hover) { + color: var(--color-red-800); + } + } + } .focus\:scale-105 { &:focus { --tw-scale-x: 105%; @@ -585,11 +989,49 @@ scale: var(--tw-scale-x) var(--tw-scale-y); } } + .focus\:border-blue-500 { + &:focus { + border-color: var(--color-blue-500); + } + } + .focus\:ring-2 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-blue-500 { + &:focus { + --tw-ring-color: var(--color-blue-500); + } + } + .focus\:ring-red-500 { + &:focus { + --tw-ring-color: var(--color-red-500); + } + } + .focus\:ring-offset-2 { + &:focus { + --tw-ring-offset-width: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } .sm\:grid-cols-3 { @media (width >= 40rem) { grid-template-columns: repeat(3, minmax(0, 1fr)); } } + .sm\:rounded-lg { + @media (width >= 40rem) { + border-radius: var(--radius-lg); + } + } .sm\:px-6 { @media (width >= 40rem) { padding-inline: calc(var(--spacing) * 6); @@ -601,6 +1043,12 @@ line-height: var(--tw-leading, var(--text-2xl--line-height)); } } + .sm\:text-sm { + @media (width >= 40rem) { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + } .md\:mb-8 { @media (width >= 48rem) { margin-bottom: calc(var(--spacing) * 8); @@ -862,6 +1310,31 @@ a.qp-a-button { scale: var(--tw-scale-x) var(--tw-scale-y); } } +@property --tw-rotate-x { + syntax: "*"; + inherits: false; + initial-value: rotateX(0); +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; + initial-value: rotateY(0); +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; + initial-value: rotateZ(0); +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; + initial-value: skewX(0); +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; + initial-value: skewY(0); +} @property --tw-space-y-reverse { syntax: "*"; inherits: false; @@ -872,6 +1345,11 @@ a.qp-a-button { inherits: false; initial-value: 0; } +@property --tw-divide-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} @property --tw-border-style { syntax: "*"; inherits: false; @@ -936,6 +1414,11 @@ a.qp-a-button { inherits: false; initial-value: 0 0 #0000; } +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} @property --tw-blur { syntax: "*"; inherits: false; @@ -976,6 +1459,42 @@ a.qp-a-button { syntax: "*"; inherits: false; } +@property --tw-backdrop-blur { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-invert { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-backdrop-sepia { + syntax: "*"; + inherits: false; +} @property --tw-duration { syntax: "*"; inherits: false; diff --git a/core/library/templates/library/base.html b/core/library/templates/library/base.html index 6c86dc3..8220a30 100644 --- a/core/library/templates/library/base.html +++ b/core/library/templates/library/base.html @@ -13,5 +13,6 @@ {% block content%}{% endblock %} {% include 'homepage/partials/_footer.html' %} + {% block extra_js %}{% endblock %} \ No newline at end of file diff --git a/core/library/templates/library/delete_confirmation.html b/core/library/templates/library/delete_confirmation.html index 9bee8fa..d320f5f 100644 --- a/core/library/templates/library/delete_confirmation.html +++ b/core/library/templates/library/delete_confirmation.html @@ -1,11 +1,39 @@ {% extends 'library/base.html' %} {% block content %} -

Löschen

-

Soll das Quiz "{{ object.name }}" wirklich gelöscht werden?

-
- {% csrf_token %} - - Abbrechen -
+
+
+
+
+ + + + +

Löschen bestätigen

+ +

+ {% if object.quiz_id %} + Möchten Sie die Frage "{{ object.data.question }}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden. + {% else %} + Möchten Sie das Quiz "{{ object.name }}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden. + {% endif %} +

+
+ +
+ {% csrf_token %} +
+ + Abbrechen + + +
+
+
+
+
{% endblock %} \ No newline at end of file diff --git a/core/library/templates/library/detail_quiz.html b/core/library/templates/library/detail_quiz.html index 0ca8b09..be78a84 100644 --- a/core/library/templates/library/detail_quiz.html +++ b/core/library/templates/library/detail_quiz.html @@ -1,19 +1,99 @@ {% extends 'library/base.html' %} {% block content %} -

{{ quiz.name }}

-

{{ quiz.description }}

-Bearbeiten -Löschen +
+
+

{{ quiz.name }}

+ +
-

Fragen

- + {% if quiz.description %} +

{{ quiz.description }}

+ {% endif %} +
+ + + {% if questions %} +
+ {% for question in questions %} +
+
+
+

{{ question.data.question }}

+
+ + {% if question.data.type == 'multiple_choice' %}Multiple Choice{% else %}Wahr/Falsch{% endif %} + +
+
+ {% if question.data.type == 'multiple_choice' %} +
    + {% for option in question.data.options %} +
  • + {% if option.is_correct %} + + + + {{ option.value }} + {% else %} + + + + {{ option.value }} + {% endif %} +
  • + {% endfor %} +
+ {% else %} +

+ Richtige Antwort: + + {% if question.data.correct_answer %}Wahr{% else %}Falsch{% endif %} + +

+ {% endif %} +
+
+ +
+
+ {% endfor %} +
+ {% else %} +
+ + + +

Noch keine Fragen vorhanden. Erstellen Sie eine neue Frage, um loszulegen!

+
+ {% endif %} +
+
{% endblock %} diff --git a/core/library/templates/library/question/question_multiple_choice.html b/core/library/templates/library/question/question_multiple_choice.html new file mode 100644 index 0000000..22c053a --- /dev/null +++ b/core/library/templates/library/question/question_multiple_choice.html @@ -0,0 +1,164 @@ +{% extends 'library/base.html' %} + +{% block content %} +
+
+

{% if question %}Frage bearbeiten{% else %}Neue Multiple Choice Frage{% endif %}

+ + Zurück zum Quiz + +
+ +
+
+ {% csrf_token %} + + +
+ +
+ +
+
+ + +
+ +
+ {% if question and question.data.options %} + {% for option in question.data.options %} +
+ + + +
+ {% endfor %} + {% else %} +
+ + + +
+
+ + + +
+ {% endif %} +
+ +
+ +
+ + Abbrechen + + +
+
+
+
+ + +{% endblock %} diff --git a/core/library/templates/library/question/question_true_false.html b/core/library/templates/library/question/question_true_false.html new file mode 100644 index 0000000..503bad5 --- /dev/null +++ b/core/library/templates/library/question/question_true_false.html @@ -0,0 +1,59 @@ +{% extends 'library/base.html' %} + +{% block content %} +
+
+

{% if question %}Frage bearbeiten{% else %}Neue Wahr/Falsch Frage{% endif %}

+ + Zurück zum Quiz + +
+ +
+
+ {% csrf_token %} + + +
+ +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + Abbrechen + + +
+
+
+
+{% endblock %} diff --git a/core/library/views.py b/core/library/views.py index 2550902..4cc3344 100644 --- a/core/library/views.py +++ b/core/library/views.py @@ -1,8 +1,11 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User +from django.urls import reverse +from django.contrib import messages from .models import QivipQuiz, QivipQuestion from .forms import QuizForm, QuestionForm +import json # Übersicht aller Quizze @login_required @@ -52,47 +55,202 @@ def delete_quiz(request, pk): def detail_quiz(request, pk): quiz = get_object_or_404(QivipQuiz, pk=pk, user_id=request.user) questions = QivipQuestion.objects.filter(quiz_id=quiz) - return render(request, 'library/detail_quiz.html', {'quiz': quiz, 'questions': questions}) + + # Parse JSON data for each question + for question in questions: + if question.data: + question.data = json.loads(question.data) + + context = { + 'quiz': quiz, + 'questions': questions + } + return render(request, 'library/detail_quiz.html', context) # Übersicht aller Fragen @login_required def question_list(request): questions = QivipQuestion.objects.filter(quiz_id__user_id=request.user) + + # Parse JSON data for each question + for question in questions: + if question.data: + question.data = json.loads(question.data) + return render(request, 'library/question_list.html', {'questions': questions}) # Neue Frage erstellen @login_required def new_question(request): + question_type = request.GET.get('type') + quiz_id = request.GET.get('quiz_id') + + if not quiz_id: + messages.error(request, 'Quiz ID muss angegeben werden.') + return redirect('library:overview_quiz') + + try: + quiz = QivipQuiz.objects.get(pk=quiz_id, user_id=request.user) + except QivipQuiz.DoesNotExist: + messages.error(request, 'Quiz nicht gefunden oder keine Berechtigung.') + return redirect('library:overview_quiz') + + if question_type not in ['multiple_choice', 'true_false']: + base_url = reverse('library:new_question') + return redirect(f'{base_url}?type=multiple_choice&quiz_id={quiz_id}') + if request.method == 'POST': - form = QuestionForm(request.POST) - if form.is_valid(): - question = form.save(commit=False) - # Prüfe ob der User Zugriff auf das Quiz hat - if question.quiz_id.user_id != request.user: - return redirect('library:question_list') - question.save() - return redirect('library:edit_question', pk=question.pk) + question_text = request.POST.get('question') + + # Handle different question types + if question_type == 'true_false': + correct_answer = request.POST.get('correct_answer') == 'true' + json_data = { + 'type': question_type, + 'question': question_text, + 'options': [ + {'value': 'Wahr', 'is_correct': correct_answer}, + {'value': 'Falsch', 'is_correct': not correct_answer} + ] + } + elif question_type == 'multiple_choice': + options = [] + i = 1 + # Limit to maximum 6 options + while request.POST.get(f'option_{i}') and i <= 6: + is_correct = request.POST.get(f'correct_{i}') == '1' + options.append({ + 'order': i, + 'value': request.POST.get(f'option_{i}'), + 'is_correct': is_correct + }) + i += 1 + + # Validate minimum 2 options + if len(options) < 2: + messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.') + return redirect(request.get_full_path()) + + json_data = { + 'type': question_type, + 'question': question_text, + 'options': options + } + + question = QivipQuestion() + question.data = json.dumps(json_data) + question.quiz_id = quiz + question.type = question_type + question.question = question_text + question.save() + return redirect('library:detail_quiz', pk=quiz.pk) else: - form = QuestionForm() - return render(request, 'library/form.html', {'form': form}) + # Initialize empty question data for new questions + question_data = { + 'type': question_type, + 'question': '', + 'options': [] + } + if question_type == 'true_false': + question_data['options'] = [ + {'value': 'Wahr', 'is_correct': True}, + {'value': 'Falsch', 'is_correct': False} + ] + elif question_type == 'multiple_choice': + question_data['options'] = [ + {'value': '', 'is_correct': False}, + {'value': '', 'is_correct': False} + ] + + template_name = f'library/question/question_{question_type}.html' + context = { + 'question': question_data, + 'quiz_id': quiz_id, + 'quiz': quiz + } + + return render(request, template_name, context) # Frage bearbeiten @login_required def edit_question(request, pk): question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user) + if question.quiz_id.user_id != request.user: + return redirect('library:question_list') + + # Parse the existing JSON data + question_data = json.loads(question.data) if question.data else {} + question_type = question_data.get('type', 'multiple_choice') + if request.method == 'POST': - form = QuestionForm(request.POST, instance=question) - if form.is_valid(): - form.save() - return redirect('library:edit_question', pk=pk) + question_text = request.POST.get('question') + + # Handle different question types + if question_type == 'true_false': + correct_answer = request.POST.get('correct_answer') == 'true' + json_data = { + 'type': question_type, + 'question': question_text, + 'correct_answer': correct_answer, + 'options': [ + {'value': 'Wahr', 'is_correct': correct_answer}, + {'value': 'Falsch', 'is_correct': not correct_answer} + ] + } + elif question_type == 'multiple_choice': + options = [] + i = 1 + # Limit to maximum 6 options + while request.POST.get(f'option_{i}') and i <= 6: + options.append({ + 'order': i, + 'value': request.POST.get(f'option_{i}'), + 'is_correct': int(request.POST.get(f'correct_{i}', 0)) + }) + i += 1 + + # Validate minimum 2 options + if len(options) < 2: + messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.') + return redirect(request.get_full_path()) + + # Validate maximum 6 options + if len(options) > 6: + messages.error(request, 'Maximal 6 Optionen sind erlaubt.') + return redirect(request.get_full_path()) + + json_data = { + 'type': question_type, + 'question': question_text, + 'options': options + } + + question.question = question_text + question.type = question_type + question.data = json.dumps(json_data) + question.save() + return redirect('library:detail_quiz', pk=question.quiz_id.pk) else: - form = QuestionForm(instance=question) - return render(request, 'library/form.html', {'form': form}) + question_data = json.loads(question.data) if question.data else {} + + template_name = f'library/question/question_{question_type}.html' + context = { + 'question': question_data, + 'quiz_id': question.quiz_id.pk, + 'quiz': question.quiz_id + } + + return render(request, template_name, context) # Frage löschen @login_required def delete_question(request, pk): question = get_object_or_404(QivipQuestion, pk=pk, quiz_id__user_id=request.user) + + # Parse JSON data for question + if question.data: + question.data = json.loads(question.data) + if request.method == 'POST': question.delete() return redirect('library:detail_quiz', pk=question.quiz_id.pk)