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, )