✨ Added button to view imported quiz on Kahoot!.
This commit is contained in:
@@ -181,6 +181,7 @@ class Quiz(ormar.Model):
|
||||
cover_image: Optional[str] = ormar.Text(nullable=True, unique=False)
|
||||
background_color: str | None = ormar.Text(nullable=True, unique=False)
|
||||
background_image: str | None = ormar.Text(nullable=True, unique=False)
|
||||
kahoot_id: uuid.UUID | None = ormar.UUID(nullable=True, default=None)
|
||||
|
||||
@validator("background_image")
|
||||
def must_come_from_local_cdn(cls, v):
|
||||
|
||||
@@ -30,7 +30,8 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
||||
:param quiz_id: The ID of the quiz to import.
|
||||
:return: True if the import was successful, False otherwise.
|
||||
"""
|
||||
quiz = await get_quiz(quiz_id)
|
||||
kahoot_quiz_id = quiz_id
|
||||
quiz = await get_quiz(kahoot_quiz_id)
|
||||
if quiz is None:
|
||||
return "quiz not found"
|
||||
quiz_questions: list[dict] = []
|
||||
@@ -60,7 +61,6 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
||||
).dict()
|
||||
)
|
||||
cover = None
|
||||
print(quiz.kahoot.cover)
|
||||
if quiz.kahoot.cover != "" and quiz.kahoot.cover is not None:
|
||||
image_bytes = await _download_image(quiz.kahoot.cover)
|
||||
image_name = f"{quiz_id}--{uuid.uuid4()}"
|
||||
@@ -77,6 +77,7 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
||||
questions=json.dumps(quiz_questions),
|
||||
imported_from_kahoot=True,
|
||||
cover_image=cover,
|
||||
kahoot_id=uuid.UUID(kahoot_quiz_id),
|
||||
)
|
||||
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz_data)])
|
||||
return await quiz_data.save()
|
||||
|
||||
@@ -105,7 +105,6 @@ async def auth(request: Request, response: Response):
|
||||
if user_data.email is None:
|
||||
return RedirectResponse("/account/oauth-error?error=email")
|
||||
user_in_db = await User.objects.get_or_none(email=user_data.email)
|
||||
print(user_data.id)
|
||||
if user_in_db is None:
|
||||
# REGISTER USER
|
||||
try:
|
||||
|
||||
@@ -92,7 +92,6 @@ async def get_public_quiz(quiz_id: str):
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="badly formed quiz id")
|
||||
quiz = await Quiz.objects.select_related("user_id").get_or_none(id=quiz_id)
|
||||
print(quiz.dict(exclude={"user_id": {"avatar"}}))
|
||||
if quiz is None:
|
||||
return JSONResponse(status_code=404, content={"detail": "quiz not found"})
|
||||
else:
|
||||
|
||||
@@ -331,8 +331,7 @@ async def get_email_from_jwt(data: GetEmailFromJWT):
|
||||
try:
|
||||
payload = jwt.decode(data.jwt, settings.secret_key, algorithms=["HS256"])
|
||||
return payload.get("sub")
|
||||
except JWTError as e:
|
||||
print(e)
|
||||
except JWTError:
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
export let flex = false;
|
||||
|
||||
export let href: undefined | string = undefined;
|
||||
export let target: undefined | string = '_self';
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
{href}
|
||||
{target}
|
||||
class="w-full px-4 py-2 leading-5 text-black dark:text-white transition-colors duration-200 transform bg-gray-50 dark:bg-gray-700 rounded text-center hover:bg-gray-300 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-600"
|
||||
on:click
|
||||
class:flex
|
||||
class:block={!flex}
|
||||
class:justify-center={flex}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import StartGamePopup from '$lib/dashboard/start_game.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
import GrayButton from '$lib/components/buttons/gray.svelte';
|
||||
|
||||
const tippy = createTippy({
|
||||
arrow: true,
|
||||
@@ -56,6 +57,7 @@
|
||||
user_id: string;
|
||||
imported_from_kahoot?: boolean;
|
||||
questions: Question[];
|
||||
kahoot_id?: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -82,87 +84,95 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="text-center text-sm pt-1">
|
||||
<div class="text-center text-sm pt-1 mb-4">
|
||||
<ImportedOrNot imported={quiz.imported_from_kahoot} />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-8 mb-4">
|
||||
{#if logged_in}
|
||||
<button
|
||||
class="admin-button"
|
||||
on:click={() => {
|
||||
start_game = quiz.id;
|
||||
}}
|
||||
>
|
||||
{$t('words.start')}
|
||||
</button>
|
||||
{:else}
|
||||
<div use:tippy={{ content: 'You need to be logged in to start a game' }}>
|
||||
<button
|
||||
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded text-center hover:bg-gray-600 focus:outline-none cursor-not-allowed opacity-50"
|
||||
disabled
|
||||
>
|
||||
{$t('words.start')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-center m-2">
|
||||
<a class="admin-button" href="/practice?quiz_id={quiz.id}">
|
||||
{$t('words.practice')}
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
{#if logged_in}
|
||||
<a
|
||||
href="/api/v1/eximport/{quiz.id}"
|
||||
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded text-center hover:bg-gray-600 focus:outline-none"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 inline-block"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
{$t('words.download')}
|
||||
</a>
|
||||
{:else}
|
||||
<div use:tippy={{ content: 'You need to be logged in to download a game' }}>
|
||||
<button
|
||||
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded text-center hover:bg-gray-600 focus:outline-none cursor-not-allowed opacity-50"
|
||||
disabled
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 inline-block"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<div class="flex flex-col justify-center">
|
||||
<div class="mx-auto flex flex-col gap-2 justify-center w-fit">
|
||||
{#if quiz.imported_from_kahoot && quiz.kahoot_id}
|
||||
<div class="w-full">
|
||||
<GrayButton
|
||||
href="https://create.kahoot.it/details/{quiz.kahoot_id}"
|
||||
target="_blank"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
{$t('words.download')}
|
||||
</button>
|
||||
View on <i>Kahoot!</i>
|
||||
</GrayButton>
|
||||
</div>
|
||||
{/if}
|
||||
{#if logged_in}
|
||||
<div class="w-full">
|
||||
<GrayButton
|
||||
on:click={() => {
|
||||
start_game = quiz.id;
|
||||
}}
|
||||
>
|
||||
{$t('words.start')}
|
||||
</GrayButton>
|
||||
</div>
|
||||
{:else}
|
||||
<div use:tippy={{ content: 'You need to be logged in to start a game' }}>
|
||||
<div class="w-full">
|
||||
<GrayButton disabled={true}>
|
||||
{$t('words.start')}
|
||||
</GrayButton>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="w-full">
|
||||
<GrayButton href="/practice?quiz_id={quiz.id}">
|
||||
{$t('words.practice')}
|
||||
</GrayButton>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<a href="mailto:report@mawoka.eu?subject=Report quiz {quiz.id}" class="text-sm underline">
|
||||
{$t('words.report')}
|
||||
</a>
|
||||
<div class="w-full">
|
||||
{#if logged_in}
|
||||
<GrayButton flex={true} href="/api/v1/eximport/{quiz.id}">
|
||||
<svg
|
||||
class="w-5 h-5 inline-block"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
{$t('words.download')}
|
||||
</GrayButton>
|
||||
{:else}
|
||||
<div use:tippy={{ content: 'You need to be logged in to download a game' }}>
|
||||
<GrayButton disabled={true} flex={true}>
|
||||
<svg
|
||||
class="w-5 h-5 inline-block"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
{$t('words.download')}
|
||||
</GrayButton>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<a
|
||||
href="mailto:report@mawoka.eu?subject=Report quiz {quiz.id}"
|
||||
class="text-sm underline"
|
||||
>
|
||||
{$t('words.report')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#each quiz.questions as question, index_question}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Added kahoot_id
|
||||
|
||||
Revision ID: 7ad8502af419
|
||||
Revises: 820e06ef2c2a
|
||||
Create Date: 2023-01-27 22:30:00.331395
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import ormar
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "7ad8502af419"
|
||||
down_revision = "820e06ef2c2a"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("quiz", sa.Column("kahoot_id", ormar.fields.sqlalchemy_uuid.CHAR(32), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("quiz", "kahoot_id")
|
||||
# ### end Alembic commands ###
|
||||
Reference in New Issue
Block a user