236 lines
9.0 KiB
Python
236 lines
9.0 KiB
Python
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",
|
|
"timezone": "America/Vancouver",
|
|
},
|
|
{
|
|
"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",
|
|
"timezone": "America/Los_Angeles",
|
|
},
|
|
{
|
|
"username": "devteam",
|
|
"email": "devteam@example.com",
|
|
"first_name": "Jordan",
|
|
"last_name": "Lee",
|
|
"password": "ClientDemo2026!",
|
|
"company": "BioFlow Analytics",
|
|
"phone": "+44 20 7946 0103",
|
|
"timezone": "Europe/London",
|
|
},
|
|
]
|
|
|
|
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"],
|
|
"timezone": client["timezone"],
|
|
},
|
|
)
|
|
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
|