From 10739f517f557a0558aa07ee8767c85b8154ea58 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 10 Nov 2025 20:56:18 +0000 Subject: [PATCH] Add command to generate sample physics questions with customizable options --- .../commands/generate_sample_questions.py | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 physics/management/commands/generate_sample_questions.py diff --git a/physics/management/commands/generate_sample_questions.py b/physics/management/commands/generate_sample_questions.py new file mode 100644 index 00000000..d4f522ce --- /dev/null +++ b/physics/management/commands/generate_sample_questions.py @@ -0,0 +1,129 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +from django.db import transaction +import random + +from physics.models import Question, Category, Exam + +User = get_user_model() + + +class Command(BaseCommand): + help = "Generate a number of sample physics questions for development/testing." + + def add_arguments(self, parser): + parser.add_argument( + "--count", + type=int, + default=10, + help="Number of sample questions to create (default: 10)", + ) + parser.add_argument( + "--category", + type=str, + default=None, + help="Category name to assign to created questions (created if missing)", + ) + parser.add_argument( + "--author", + type=str, + default=None, + help="Username of an existing user to add as author for created questions", + ) + parser.add_argument( + "--seed", + type=int, + default=None, + help="Optional random seed for reproducible generation", + ) + parser.add_argument( + "--create-exam", + dest="create_exam", + type=str, + default=None, + help="If provided, create an exam with this name and add generated questions to it", + ) + parser.add_argument( + "--activate", + action="store_true", + dest="activate", + help="If set when creating an exam, mark the exam active", + ) + + def handle(self, *args, **options): + count = options.get("count") or 10 + category_name = options.get("category") + author_username = options.get("author") + seed = options.get("seed") + + if seed is not None: + random.seed(seed) + + category = None + if category_name: + category, _ = Category.objects.get_or_create(category=category_name) + + created_exam = None + create_exam_name = options.get("create_exam") + activate = options.get("activate") + if create_exam_name: + created_exam = Exam.objects.create(name=create_exam_name, active=bool(activate)) + + author_user = None + if author_username: + try: + author_user = User.objects.get(username=author_username) + except User.DoesNotExist: + self.stdout.write(self.style.WARNING(f"Author username '{author_username}' not found; continuing without author")) + author_user = None + + created = 0 + + with transaction.atomic(): + for i in range(count): + qnum = Question.objects.count() + 1 + stem = f"Sample physics question #{qnum}: Identify which of the following statements are true." + + # simple option texts + opts = [ + f"Statement A for question {qnum}", + f"Statement B for question {qnum}", + f"Statement C for question {qnum}", + f"Statement D for question {qnum}", + f"Statement E for question {qnum}", + ] + + # randomly decide true/false for each option but ensure at least one True + answers = [random.choice([True, False]) for _ in range(5)] + if not any(answers): + answers[random.randrange(5)] = True + + q = Question.objects.create( + stem=stem, + a=opts[0], + a_answer=answers[0], + a_feedback="", + b=opts[1], + b_answer=answers[1], + b_feedback="", + c=opts[2], + c_answer=answers[2], + c_feedback="", + d=opts[3], + d_answer=answers[3], + d_feedback="", + e=opts[4], + e_answer=answers[4], + e_feedback="", + category=category, + ) + + # If we created an exam, add this question to it + if created_exam: + created_exam.exam_questions.add(q) + if author_user: + q.author.add(author_user) + + created += 1 + + self.stdout.write(self.style.SUCCESS(f"Created {created} sample physics question(s)."))