from datetime import date from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand from django.db import transaction from django.db.models.signals import post_save, pre_save from apps.accounts.models import ClientProfile from apps.projects.models import ProjectBrief, ProjectInternalNote from apps.projects.signals import capture_previous_status, handle_brief_notifications CLIENTS = [ { "username": "client1", "email": "client1@example.com", "first_name": "Alex", "last_name": "Morgan", "password": "ClientDemo2026!", "company": "Northside Imaging Lab", "phone": "+1 604 555 0101", }, { "username": "sarah.research", "email": "sarah.research@example.com", "first_name": "Sarah", "last_name": "Chen", "password": "ClientDemo2026!", "company": "Pacific Oncology Research", "phone": "+1 604 555 0102", }, { "username": "devteam", "email": "devteam@example.com", "first_name": "Jordan", "last_name": "Lee", "password": "ClientDemo2026!", "company": "BioFlow Analytics", "phone": "+44 20 7946 0103", }, ] PROJECTS = [ { "username": "client1", "title": "Radiomics pipeline audit", "category": ProjectBrief.CATEGORY_MEDICAL_IMAGING, "description": ( "We need Tecvico to review our current radiomics extraction workflow " "and align it with IBSI 2.0 standards across PET/CT datasets.\n\n" "Goals: Standardize preprocessing, document gaps, and deliver a " "remediation plan." ), "budget_range": ProjectBrief.BUDGET_5K_15K, "desired_deadline": date(2026, 9, 30), "reference_links": "https://example.com/radiomics-spec\nhttps://example.com/sample-dataset", "status": ProjectBrief.STATUS_UNDER_REVIEW, "quote_text": "", "internal_notes": ["Client has 120-case retrospective cohort ready."], }, { "username": "client1", "title": "Clinical trial dashboard", "category": ProjectBrief.CATEGORY_WEB, "description": ( "Build a secure web dashboard for monitoring recruitment, imaging QC, " "and milestone completion across two active trials.\n\n" "Goals: Role-based views for coordinators and PI weekly status exports." ), "budget_range": ProjectBrief.BUDGET_15K_50K, "desired_deadline": date(2026, 11, 15), "reference_links": "https://example.com/wireframes", "status": ProjectBrief.STATUS_QUOTED, "quote_text": ( "Phase 1 discovery and UX: $8,500\n" "Phase 2 build and QA: $22,000\n" "Estimated timeline: 10 weeks." ), "internal_notes": ["Needs SSO discussion on kickoff call."], }, { "username": "sarah.research", "title": "Workflow automation for cohort exports", "category": ProjectBrief.CATEGORY_WORKFLOW, "description": ( "Automate export of segmented lesions and feature tables from Tecvico " "into our downstream R analysis environment.\n\n" "Goals: One-click export with audit log and reproducible config files." ), "budget_range": ProjectBrief.BUDGET_5K_15K, "desired_deadline": date(2026, 8, 1), "reference_links": "", "status": ProjectBrief.STATUS_IN_PROGRESS, "quote_text": "Fixed fee $11,500 — delivery in 6 weeks with two revision rounds.", "internal_notes": ["Milestone 1 approved.", "Waiting on sample DICOM push."], }, { "username": "sarah.research", "title": "Publication figure package", "category": ProjectBrief.CATEGORY_RESEARCH, "description": ( "Prepare publication-ready figures and methods appendix for a radiomics " "paper.\n\n" "Goals: Journal-compliant figures, caption draft, and reproducibility " "checklist." ), "budget_range": ProjectBrief.BUDGET_UNDER_5K, "desired_deadline": date(2026, 7, 20), "reference_links": "https://example.com/journal-guidelines", "status": ProjectBrief.STATUS_DELIVERED, "quote_text": "", "internal_notes": ["Delivered v2 figures on June 12."], }, { "username": "devteam", "title": "Multi-site data harmonization study", "category": ProjectBrief.CATEGORY_DATA, "description": ( "Assess batch effects across three hospital sites and propose " "harmonization strategy before model training.\n\n" "Goals: Site effect report, recommended normalization approach, and " "pilot notebook." ), "budget_range": ProjectBrief.BUDGET_50K_PLUS, "desired_deadline": date(2027, 1, 31), "reference_links": "https://example.com/data-dictionary", "status": ProjectBrief.STATUS_SUBMITTED, "quote_text": "", "internal_notes": [], }, ] class Command(BaseCommand): help = "Seed demo client accounts and project briefs for the managed projects portal." def add_arguments(self, parser): parser.add_argument( "--flush", action="store_true", help="Delete seeded demo users and their project briefs before re-seeding.", ) def handle(self, *args, **options): post_save.disconnect(handle_brief_notifications, sender=ProjectBrief) pre_save.disconnect(capture_previous_status, sender=ProjectBrief) try: with transaction.atomic(): if options["flush"]: self._flush() users = self._seed_clients() count = self._seed_projects(users) finally: post_save.connect(handle_brief_notifications, sender=ProjectBrief) pre_save.connect(capture_previous_status, sender=ProjectBrief) self.stdout.write(self.style.SUCCESS(f"Seeded {len(users)} clients and {count} project briefs.")) self.stdout.write("Demo login: client1 / ClientDemo2026!") def _flush(self): User = get_user_model() usernames = [client["username"] for client in CLIENTS] users = User.objects.filter(username__in=usernames) ProjectBrief.objects.filter(client__in=users).delete() ClientProfile.objects.filter(user__in=users).delete() deleted, _ = users.delete() self.stdout.write(f" Removed {deleted} demo user records") def _seed_clients(self): User = get_user_model() users = {} for client in CLIENTS: user, created = User.objects.get_or_create( username=client["username"], defaults={ "email": client["email"], "first_name": client["first_name"], "last_name": client["last_name"], }, ) user.email = client["email"] user.first_name = client["first_name"] user.last_name = client["last_name"] user.set_password(client["password"]) user.save() ClientProfile.objects.update_or_create( user=user, defaults={ "company": client["company"], "phone": client["phone"], }, ) action = "Created" if created else "Updated" self.stdout.write(f" {action} client: {user.username}") users[user.username] = user return users def _seed_projects(self, users): admin = get_user_model().objects.filter(is_superuser=True).first() count = 0 for project in PROJECTS: client = users[project["username"]] brief, created = ProjectBrief.objects.update_or_create( client=client, title=project["title"], defaults={ "category": project["category"], "description": project["description"], "budget_range": project["budget_range"], "desired_deadline": project["desired_deadline"], "reference_links": project["reference_links"], "status": project["status"], "quote_text": project["quote_text"], }, ) if created: count += 1 action = "Created" else: action = "Updated" self.stdout.write(f" {action} project: {brief.title}") if project["internal_notes"] and admin: for note in project["internal_notes"]: ProjectInternalNote.objects.get_or_create( brief=brief, note=note, defaults={"author": admin}, ) return count