181 lines
6.5 KiB
Python
181 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.models import AbstractBaseUser
|
|
|
|
from .models import Category, Question, QuestionLie
|
|
|
|
DEFAULT_MVP_HOST_USERNAME = "demo-host"
|
|
DEFAULT_MVP_HOST_PASSWORD = "demo-pass"
|
|
DEFAULT_MVP_CATEGORY_SLUG = "general"
|
|
DEFAULT_MVP_CATEGORY_NAME = "General"
|
|
DEFAULT_MVP_QUESTIONS: tuple[tuple[str, str], ...] = (
|
|
("What is the capital of Denmark?", "Copenhagen"),
|
|
("Which planet is known as the Red Planet?", "Mars"),
|
|
("How many players are required before the host can start a round?", "3"),
|
|
)
|
|
DEFAULT_MVP_FALLBACK_LIES_BY_PROMPT: dict[str, tuple[str, ...]] = {
|
|
"What is the capital of Denmark?": ("Aarhus", "Odense", "Aalborg", "Roskilde", "Esbjerg"),
|
|
"Which planet is known as the Red Planet?": ("Venus", "Jupiter", "Saturn", "Mercury", "Neptune"),
|
|
"How many players are required before the host can start a round?": ("2", "4", "5", "6", "8"),
|
|
}
|
|
DEFAULT_MVP_SCENE_ORNAMENT_BY_PROMPT: dict[str, str] = {
|
|
"What is the capital of Denmark?": Question.SceneOrnament.HARBOR_FLARE,
|
|
"Which planet is known as the Red Planet?": Question.SceneOrnament.AURORA_ARC,
|
|
"How many players are required before the host can start a round?": Question.SceneOrnament.SIGNAL_BLOOM,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SeedSummary:
|
|
created: int
|
|
updated: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MvpBootstrapResult:
|
|
host: AbstractBaseUser
|
|
category: Category
|
|
questions: tuple[Question, ...]
|
|
host_changes: SeedSummary
|
|
category_changes: SeedSummary
|
|
question_changes: SeedSummary
|
|
|
|
|
|
def ensure_host_user(*, username: str, password: str, is_staff: bool = True) -> tuple[AbstractBaseUser, SeedSummary]:
|
|
user_model = get_user_model()
|
|
host, created = user_model.objects.get_or_create(username=username)
|
|
|
|
updates: list[str] = []
|
|
if not host.is_active:
|
|
host.is_active = True
|
|
updates.append("is_active")
|
|
if host.is_staff != is_staff:
|
|
host.is_staff = is_staff
|
|
updates.append("is_staff")
|
|
|
|
host.set_password(password)
|
|
updates.append("password")
|
|
host.save(update_fields=updates)
|
|
return host, SeedSummary(created=int(created), updated=int(bool(updates and not created)))
|
|
|
|
|
|
def ensure_category_with_questions(
|
|
*,
|
|
slug: str,
|
|
name: str,
|
|
prompts_and_answers: tuple[tuple[str, str], ...],
|
|
fallback_lies_by_prompt: dict[str, tuple[str, ...]] | None = None,
|
|
scene_ornament_by_prompt: dict[str, str] | None = None,
|
|
) -> tuple[Category, tuple[Question, ...], SeedSummary, SeedSummary]:
|
|
category, created = Category.objects.get_or_create(
|
|
slug=slug,
|
|
defaults={"name": name, "is_active": True},
|
|
)
|
|
|
|
category_updates: list[str] = []
|
|
if category.name != name:
|
|
category.name = name
|
|
category_updates.append("name")
|
|
if not category.is_active:
|
|
category.is_active = True
|
|
category_updates.append("is_active")
|
|
if category_updates:
|
|
category.save(update_fields=category_updates)
|
|
|
|
questions: list[Question] = []
|
|
created_count = 0
|
|
updated_count = 0
|
|
for prompt, correct_answer in prompts_and_answers:
|
|
scene_ornament = ""
|
|
if scene_ornament_by_prompt:
|
|
scene_ornament = scene_ornament_by_prompt.get(prompt, "")
|
|
question, question_created = Question.objects.get_or_create(
|
|
category=category,
|
|
prompt=prompt,
|
|
defaults={
|
|
"correct_answer": correct_answer,
|
|
"scene_ornament": scene_ornament,
|
|
"is_active": True,
|
|
},
|
|
)
|
|
question_updates: list[str] = []
|
|
if question.correct_answer != correct_answer:
|
|
question.correct_answer = correct_answer
|
|
question_updates.append("correct_answer")
|
|
if question.scene_ornament != scene_ornament:
|
|
question.scene_ornament = scene_ornament
|
|
question_updates.append("scene_ornament")
|
|
if not question.is_active:
|
|
question.is_active = True
|
|
question_updates.append("is_active")
|
|
if question_updates:
|
|
question.save(update_fields=question_updates)
|
|
if fallback_lies_by_prompt:
|
|
ensure_question_fallback_lies(
|
|
question=question,
|
|
lies=fallback_lies_by_prompt.get(prompt, ()),
|
|
)
|
|
created_count += int(question_created)
|
|
updated_count += int(bool(question_updates and not question_created))
|
|
questions.append(question)
|
|
|
|
return (
|
|
category,
|
|
tuple(questions),
|
|
SeedSummary(created=int(created), updated=int(bool(category_updates and not created))),
|
|
SeedSummary(created=created_count, updated=updated_count),
|
|
)
|
|
|
|
|
|
def ensure_question_fallback_lies(*, question: Question, lies: tuple[str, ...]) -> SeedSummary:
|
|
created_count = 0
|
|
updated_count = 0
|
|
for index, lie_text in enumerate(lies):
|
|
lie, created = QuestionLie.objects.get_or_create(
|
|
question=question,
|
|
text=lie_text,
|
|
defaults={"is_active": True, "sort_order": index},
|
|
)
|
|
updates: list[str] = []
|
|
if not lie.is_active:
|
|
lie.is_active = True
|
|
updates.append("is_active")
|
|
if lie.sort_order != index:
|
|
lie.sort_order = index
|
|
updates.append("sort_order")
|
|
if updates:
|
|
lie.save(update_fields=updates)
|
|
created_count += int(created)
|
|
updated_count += int(bool(updates and not created))
|
|
|
|
return SeedSummary(created=created_count, updated=updated_count)
|
|
|
|
|
|
def ensure_mvp_bootstrap(
|
|
*,
|
|
username: str = DEFAULT_MVP_HOST_USERNAME,
|
|
password: str = DEFAULT_MVP_HOST_PASSWORD,
|
|
category_slug: str = DEFAULT_MVP_CATEGORY_SLUG,
|
|
category_name: str = DEFAULT_MVP_CATEGORY_NAME,
|
|
prompts_and_answers: tuple[tuple[str, str], ...] = DEFAULT_MVP_QUESTIONS,
|
|
) -> MvpBootstrapResult:
|
|
host, host_changes = ensure_host_user(username=username, password=password)
|
|
category, questions, category_changes, question_changes = ensure_category_with_questions(
|
|
slug=category_slug,
|
|
name=category_name,
|
|
prompts_and_answers=prompts_and_answers,
|
|
fallback_lies_by_prompt=DEFAULT_MVP_FALLBACK_LIES_BY_PROMPT,
|
|
scene_ornament_by_prompt=DEFAULT_MVP_SCENE_ORNAMENT_BY_PROMPT,
|
|
)
|
|
return MvpBootstrapResult(
|
|
host=host,
|
|
category=category,
|
|
questions=questions,
|
|
host_changes=host_changes,
|
|
category_changes=category_changes,
|
|
question_changes=question_changes,
|
|
)
|