fix: separate logo and hero icon
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@ class SiteBrandingAdmin(admin.ModelAdmin):
|
||||
),
|
||||
"description": (
|
||||
"Manage each placement independently. Removing an upload restores "
|
||||
"the existing Radiuma asset for that placement."
|
||||
"the existing Communication to Care asset for that placement."
|
||||
),
|
||||
},
|
||||
),
|
||||
|
||||
@@ -4,29 +4,53 @@ from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
DEFAULT_ADMIN_PASSWORD = "cmosV6Tw46Odv7UN"
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Create a superuser from environment variables if one does not already exist."
|
||||
help = "Create the first admin account from environment variables when needed."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
User = get_user_model()
|
||||
|
||||
username = os.environ.get("DJANGO_SUPERUSER_USERNAME", "admin")
|
||||
email = os.environ.get("DJANGO_SUPERUSER_EMAIL", "admin@radiuma.com")
|
||||
password = os.environ.get("DJANGO_SUPERUSER_PASSWORD")
|
||||
enabled = os.environ.get("DJANGO_SUPERUSER_ENABLED", "True").lower()
|
||||
if enabled in {"0", "false", "no", "off"}:
|
||||
self.stdout.write("Automatic admin creation is disabled.")
|
||||
return
|
||||
|
||||
if not password:
|
||||
username = os.environ.get("DJANGO_SUPERUSER_USERNAME", "admin")
|
||||
email = os.environ.get("DJANGO_SUPERUSER_EMAIL", "admin@com2care.com")
|
||||
password = os.environ.get("DJANGO_SUPERUSER_PASSWORD", DEFAULT_ADMIN_PASSWORD)
|
||||
sync_password = os.environ.get(
|
||||
"DJANGO_SUPERUSER_SYNC_PASSWORD", "False"
|
||||
).lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
user = User.objects.filter(username=username).first()
|
||||
if user:
|
||||
if sync_password:
|
||||
user.email = email
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.set_password(password)
|
||||
user.save(
|
||||
update_fields=["email", "is_staff", "is_superuser", "password"]
|
||||
)
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Admin '{username}' credentials synchronized from the environment."
|
||||
)
|
||||
)
|
||||
return
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
"DJANGO_SUPERUSER_PASSWORD is not set — skipping superuser creation."
|
||||
self.style.SUCCESS(
|
||||
f"Admin '{username}' already exists; its password was preserved."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if User.objects.filter(username=username).exists():
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f"Superuser '{username}' already exists — skipping.")
|
||||
)
|
||||
return
|
||||
|
||||
User.objects.create_superuser(username=username, email=email, password=password)
|
||||
self.stdout.write(self.style.SUCCESS(f"Superuser '{username}' created successfully."))
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Admin '{username}' created. Change the password in Django Admin after first login."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import re
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import models, transaction
|
||||
|
||||
|
||||
LEGACY_NAME = "".join(("Radi", "uma"))
|
||||
LEGACY_PATTERN = re.compile(re.escape(LEGACY_NAME), re.IGNORECASE)
|
||||
LEGACY_DOMAIN_PATTERN = re.compile(
|
||||
rf"{re.escape(LEGACY_NAME)}\.com", re.IGNORECASE
|
||||
)
|
||||
PUBLIC_APP_LABELS = frozenset({"core", "pages", "products"})
|
||||
|
||||
|
||||
def branded_value(field, value):
|
||||
if not isinstance(value, str) or not LEGACY_PATTERN.search(value):
|
||||
return value
|
||||
value = LEGACY_DOMAIN_PATTERN.sub("com2care.com", value)
|
||||
if isinstance(field, (models.EmailField, models.URLField, models.SlugField)):
|
||||
return LEGACY_PATTERN.sub("com2care", value)
|
||||
return LEGACY_PATTERN.sub("Communication to Care", value)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Normalize legacy public content to the current com2care identity."
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
updated_rows = 0
|
||||
for model in apps.get_models():
|
||||
if model._meta.app_label not in PUBLIC_APP_LABELS:
|
||||
continue
|
||||
text_fields = [
|
||||
field
|
||||
for field in model._meta.concrete_fields
|
||||
if isinstance(field, (models.CharField, models.TextField))
|
||||
and not field.primary_key
|
||||
]
|
||||
if not text_fields:
|
||||
continue
|
||||
|
||||
field_names = [field.name for field in text_fields]
|
||||
for instance in model._default_manager.all().only("pk", *field_names).iterator():
|
||||
changed_fields = []
|
||||
for field in text_fields:
|
||||
current = getattr(instance, field.name)
|
||||
updated = branded_value(field, current)
|
||||
if updated == current:
|
||||
continue
|
||||
if isinstance(field, models.SlugField):
|
||||
conflict = model._default_manager.exclude(pk=instance.pk).filter(
|
||||
**{field.name: updated}
|
||||
).exists()
|
||||
if conflict:
|
||||
continue
|
||||
setattr(instance, field.name, updated)
|
||||
changed_fields.append(field.name)
|
||||
if changed_fields:
|
||||
instance.save(update_fields=changed_fields)
|
||||
updated_rows += 1
|
||||
|
||||
if updated_rows:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f"Normalized {updated_rows} public content row(s).")
|
||||
)
|
||||
else:
|
||||
self.stdout.write("Public content already uses the com2care identity.")
|
||||
@@ -1,24 +1,38 @@
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from apps.core.models import SiteContact
|
||||
from apps.pages.models import DownloadItem, FAQEntry, HeroSection, HomepageSection, HomepageSectionItem
|
||||
from apps.products.models import Article, ArticleSection, MainProduct, SubProduct
|
||||
from apps.core.models import SiteBranding, SiteContact
|
||||
from apps.pages.models import (
|
||||
AboutSection,
|
||||
AboutSectionItem,
|
||||
DownloadItem,
|
||||
FAQEntry,
|
||||
HeroSection,
|
||||
HomepageSection,
|
||||
HomepageSectionItem,
|
||||
PageVideo,
|
||||
)
|
||||
from apps.products.models import (
|
||||
Article,
|
||||
ArticleSection,
|
||||
MainProduct,
|
||||
ProductVideo,
|
||||
SubProduct,
|
||||
)
|
||||
|
||||
MAIN_PRODUCTS = [
|
||||
{
|
||||
"name": "Radiuma",
|
||||
"slug": "radiuma",
|
||||
"short_description": "Visualized & Standardized Environment for Radiomics Analysis",
|
||||
"name": "Communication to Care",
|
||||
"slug": "com2care",
|
||||
"short_description": "Collaborative, standardized medical imaging research workflows",
|
||||
"description": (
|
||||
"Radiuma is a free, open-source software specialized for visualization, "
|
||||
"processing, segmentation, registration, fusion and analysis of medical and "
|
||||
"biomedical images, including radiomics and machine learning analysis. "
|
||||
"Radiuma is a major, entirely-revamped upgrade to the original SERA "
|
||||
"(Matlab-based), now built on Python for broader accessibility and community "
|
||||
"contribution. It enables standardized and reproducible radiomic feature "
|
||||
"extraction in compliance with the Image Biomarker Standardization Initiative "
|
||||
"(IBSI 1.0), and implements image filters standardized against IBSI 2.0."
|
||||
"Communication to Care (com2care) brings medical-image visualization, processing, "
|
||||
"segmentation, registration, fusion, radiomics, and machine-learning workflows into "
|
||||
"one research environment. The platform is designed to help multidisciplinary teams "
|
||||
"discuss findings clearly, build repeatable pipelines, and share analysis context. "
|
||||
"Its quantitative imaging workflow follows IBSI guidance for reproducible research."
|
||||
),
|
||||
"order": 1,
|
||||
"show_on_homepage": True,
|
||||
@@ -31,7 +45,7 @@ MAIN_PRODUCTS = [
|
||||
"description": (
|
||||
"Advanced image processing capabilities including standardized filtering "
|
||||
"techniques compliant with IBSI 2.0, image registration, fusion, and "
|
||||
"Standardized Uptake Value (SUV) conversion. Radiuma employs popular "
|
||||
"Standardized Uptake Value (SUV) conversion. Communication to Care employs popular "
|
||||
"image processing algorithms to create end-to-end standardized workflows "
|
||||
"for consistent, reproducible research outcomes."
|
||||
),
|
||||
@@ -40,7 +54,7 @@ MAIN_PRODUCTS = [
|
||||
{
|
||||
"title": "Image Filtering Techniques",
|
||||
"description": (
|
||||
"Radiuma implements a comprehensive set of image filtering techniques "
|
||||
"Communication to Care implements a comprehensive set of image filtering techniques "
|
||||
"fully standardized against the Image Biomarker Standardization "
|
||||
"Initiative (IBSI) phase 2. These filters enable reproducible "
|
||||
"preprocessing across institutions and studies."
|
||||
@@ -53,13 +67,13 @@ MAIN_PRODUCTS = [
|
||||
"value": "Mean, Gaussian, Laplacian of Gaussian (LoG), Laws kernels, Gabor, Wavelets (PyWavelets), Log-Sigma",
|
||||
"order": 2,
|
||||
},
|
||||
{"title": "Author", "value": "Radiuma R&D Team", "order": 3},
|
||||
{"title": "Author", "value": "Communication to Care R&D Team", "order": 3},
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "Image Registration & Fusion",
|
||||
"description": (
|
||||
"Radiuma provides robust image registration and fusion methods, "
|
||||
"Communication to Care provides robust image registration and fusion methods, "
|
||||
"enabling multi-modal image alignment for PET/CT, PET/MRI, and "
|
||||
"other combined modality studies. Standardized Uptake Value (SUV) "
|
||||
"conversion is also supported."
|
||||
@@ -78,7 +92,7 @@ MAIN_PRODUCTS = [
|
||||
"slug": "radiomics-features",
|
||||
"short_description": "IBSI 1.0 compliant handcrafted radiomic feature extraction",
|
||||
"description": (
|
||||
"Radiuma provides comprehensive handcrafted radiomic feature extraction "
|
||||
"Communication to Care provides comprehensive handcrafted radiomic feature extraction "
|
||||
"fully standardized by the Image Biomarker Standardization Initiative "
|
||||
"(IBSI 1.0). Features are computed from segmented regions of interest "
|
||||
"across multiple image modalities, enabling reproducible quantitative "
|
||||
@@ -89,7 +103,7 @@ MAIN_PRODUCTS = [
|
||||
{
|
||||
"title": "IBSI Compliant Feature Extraction",
|
||||
"description": (
|
||||
"Radiuma computes a comprehensive set of radiomic features "
|
||||
"Communication to Care computes a comprehensive set of radiomic features "
|
||||
"covering all IBSI 1.0 feature classes. Features are extracted "
|
||||
"from segmented Regions of Interest (ROIs) and are fully "
|
||||
"reproducible across different platforms and institutions."
|
||||
@@ -113,7 +127,7 @@ MAIN_PRODUCTS = [
|
||||
"slug": "medical-image-visualization",
|
||||
"short_description": "Professional multi-modality medical image viewer",
|
||||
"description": (
|
||||
"Radiuma includes a professional medical image viewer that supports "
|
||||
"Communication to Care includes a professional medical image viewer that supports "
|
||||
"multiple imaging modalities and file formats. The viewer provides "
|
||||
"comfortable, intuitive controls for slice navigation, windowing, "
|
||||
"zoom, and annotation, suitable for radiation oncologists, radiologists, "
|
||||
@@ -143,7 +157,7 @@ MAIN_PRODUCTS = [
|
||||
"slug": "format-conversion",
|
||||
"short_description": "Professional converter for medical imaging file formats",
|
||||
"description": (
|
||||
"Radiuma provides a professional image format converter supporting all "
|
||||
"Communication to Care provides a professional image format converter supporting all "
|
||||
"major medical imaging standards. Seamlessly convert between DICOM, "
|
||||
"NIFTI, NRRD, MHA, and other formats without loss of spatial metadata "
|
||||
"or patient information integrity."
|
||||
@@ -172,7 +186,7 @@ MAIN_PRODUCTS = [
|
||||
"slug": "workflow-management",
|
||||
"short_description": "Reproducible research workflow creation and sharing",
|
||||
"description": (
|
||||
"Radiuma's workflow management system allows researchers to design, save, "
|
||||
"Communication to Care's workflow management system allows researchers to design, save, "
|
||||
"share, and reuse analysis pipelines. Workflows connect individual "
|
||||
"processing steps — from image loading and preprocessing to feature "
|
||||
"extraction and machine learning — into reproducible, shareable sequences "
|
||||
@@ -202,34 +216,28 @@ MAIN_PRODUCTS = [
|
||||
|
||||
FAQ_ENTRIES = [
|
||||
{
|
||||
"question": "What is the Radiuma license?",
|
||||
"question": "How is Communication to Care licensed?",
|
||||
"answer": (
|
||||
"Radiuma is free and open-source for research purposes.\n\n"
|
||||
"License: CC BY-NC-SA (Creative Commons Attribution-NonCommercial-ShareAlike). "
|
||||
"This means you may use, share, and adapt the software for non-commercial "
|
||||
"research purposes, provided you give appropriate credit and distribute "
|
||||
"derivatives under the same license."
|
||||
"Licensing and deployment terms are provided with each com2care release. "
|
||||
"Contact support@com2care.com for research, institutional, or evaluation access."
|
||||
),
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
"question": "How do I cite Radiuma in my research?",
|
||||
"question": "How do I acknowledge Communication to Care in my research?",
|
||||
"answer": (
|
||||
"Please cite the following reference if you publish results obtained with "
|
||||
"the help of Radiuma:\n\n"
|
||||
"M. R. Salmanpour, I. Shiri, M. Hosseinzadeh, H. Zaidi, S. Ashrafinia, "
|
||||
"M. Oveisi, A. Rahmim. Radiuma: Visualized & Standardized Environment for "
|
||||
"Radiomics Analysis — A Shareable, Executable, and Reproducible Workflow "
|
||||
"Generator. Proc. IEEE Medical Imaging Conference, 2023."
|
||||
"Mention Communication to Care (com2care) and the software version used in your "
|
||||
"methods section. Release-specific citation guidance can be requested from "
|
||||
"support@com2care.com."
|
||||
),
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
"question": "Which operating systems does Radiuma support?",
|
||||
"question": "Which operating systems does Communication to Care support?",
|
||||
"answer": (
|
||||
"Radiuma currently fully supports Windows 10 and above (64-bit). "
|
||||
"New versions to support macOS and Linux systems are under active development "
|
||||
"and coming soon. Follow our Discord or check each product module page for updates."
|
||||
"Communication to Care currently fully supports Windows 10 and above (64-bit). "
|
||||
"macOS and Linux packages are represented in this demonstration dataset as upcoming "
|
||||
"channels. Check each product module page for current release information."
|
||||
),
|
||||
"order": 3,
|
||||
},
|
||||
@@ -244,9 +252,9 @@ FAQ_ENTRIES = [
|
||||
"order": 4,
|
||||
},
|
||||
{
|
||||
"question": "Is Radiuma suitable for clinical use?",
|
||||
"question": "Is Communication to Care suitable for clinical use?",
|
||||
"answer": (
|
||||
"Radiuma is designed and intended exclusively for research purposes. "
|
||||
"Communication to Care is designed and intended exclusively for research purposes. "
|
||||
"It is not certified for clinical diagnostic use. Always consult with "
|
||||
"qualified medical professionals for clinical decisions."
|
||||
),
|
||||
@@ -255,9 +263,8 @@ FAQ_ENTRIES = [
|
||||
{
|
||||
"question": "Where can I get support or report issues?",
|
||||
"answer": (
|
||||
"Support is available via email and through our community Discord server "
|
||||
"(see the Contact page for current details). For bug reports and feature "
|
||||
"requests, please use the Discord forum or contact us directly by email."
|
||||
"Use the contact form or email support@com2care.com. Include the software version, "
|
||||
"operating system, a short reproduction description, and non-sensitive logs when relevant."
|
||||
),
|
||||
"order": 6,
|
||||
},
|
||||
@@ -282,9 +289,42 @@ HOMEPAGE_SECTIONS = [
|
||||
{
|
||||
"section_type": HomepageSection.TYPE_SCREENSHOTS,
|
||||
"badge": "Gallery",
|
||||
"title": "See Radiuma in Action",
|
||||
"description": "Explore Radiuma's powerful interface, workflow builder, and multi-modal image viewer.",
|
||||
"title": "See Communication to Care in Action",
|
||||
"description": "Explore Communication to Care's powerful interface, workflow builder, and multi-modal image viewer.",
|
||||
"order": 2,
|
||||
"items": [
|
||||
{
|
||||
"title": "Visual workflow builder",
|
||||
"content": "Connect image-processing steps into a repeatable analysis pipeline.",
|
||||
"static_image": "screenshot-3.png",
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
"title": "Multi-planar image review",
|
||||
"content": "Inspect imaging and segmentation context across synchronized views.",
|
||||
"static_image": "screenshot-4.png",
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
"title": "Radiomics configuration",
|
||||
"content": "Review quantitative feature settings before a reproducible run.",
|
||||
"static_image": "screenshot-5.png",
|
||||
"order": 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"section_type": HomepageSection.TYPE_VIDEO,
|
||||
"badge": "Learning Library",
|
||||
"title": "Medical image segmentation essentials",
|
||||
"description": (
|
||||
"A practical introduction to thresholding, drawing, erasing, and 3D review in a "
|
||||
"medical-image segmentation workflow."
|
||||
),
|
||||
"video_url": "https://www.youtube.com/watch?v=_9J3i883yA4",
|
||||
"video_styled_background": True,
|
||||
"video_size": "lg",
|
||||
"order": 3,
|
||||
"items": [],
|
||||
},
|
||||
{
|
||||
@@ -292,19 +332,19 @@ HOMEPAGE_SECTIONS = [
|
||||
"badge": "Our Software",
|
||||
"title": "Products",
|
||||
"description": "Explore our suite of medical imaging and radiomics tools.",
|
||||
"order": 3,
|
||||
"order": 4,
|
||||
"items": [],
|
||||
},
|
||||
{
|
||||
"section_type": HomepageSection.TYPE_PROBLEMS,
|
||||
"badge": "Value Proposition",
|
||||
"title": "What Problems Does Radiuma Solve?",
|
||||
"title": "What Problems Does Communication to Care Solve?",
|
||||
"description": "",
|
||||
"order": 4,
|
||||
"order": 5,
|
||||
"items": [
|
||||
{"icon": "01", "title": "Accessibility", "content": "Radiuma provides a user-friendly interface and a wide range of tools, allowing researchers to perform complex data analysis without extensive technical knowledge or programming expertise.", "order": 1},
|
||||
{"icon": "02", "title": "Integrated Tools", "content": "Radiuma integrates a vast collection of tools and resources from various domains of healthcare and medical imaging research in a common, unified environment.", "order": 2},
|
||||
{"icon": "03", "title": "Flexibility", "content": "Radiuma offers flexibility in terms of tool optimization and workflow customization to match your specific research requirements.", "order": 3},
|
||||
{"icon": "01", "title": "Accessibility", "content": "Communication to Care provides a user-friendly interface and a wide range of tools, allowing researchers to perform complex data analysis without extensive technical knowledge or programming expertise.", "order": 1},
|
||||
{"icon": "02", "title": "Integrated Tools", "content": "Communication to Care integrates a vast collection of tools and resources from various domains of healthcare and medical imaging research in a common, unified environment.", "order": 2},
|
||||
{"icon": "03", "title": "Flexibility", "content": "Communication to Care offers flexibility in terms of tool optimization and workflow customization to match your specific research requirements.", "order": 3},
|
||||
{"icon": "04", "title": "Reproducibility", "content": "Improve usability, reusability, and reproducibility (URR) through a workflow management system that allows researchers to easily create, share, and reuse analysis pipelines.", "order": 4},
|
||||
],
|
||||
},
|
||||
@@ -313,48 +353,112 @@ HOMEPAGE_SECTIONS = [
|
||||
"badge": "Our Story",
|
||||
"title": "More to Know",
|
||||
"description": (
|
||||
"Radiuma has been developing since 2021 by the Quantitative Radiomolecular Imaging "
|
||||
"and Therapy (Qurit) lab & program at the University of British Columbia & "
|
||||
"BC Cancer Research Institute, Vancouver, BC, Canada."
|
||||
"Communication to Care is shaped around multidisciplinary research: connect imaging "
|
||||
"evidence, analysis steps, and team discussion in one understandable workflow."
|
||||
),
|
||||
"link_text": "Learn More",
|
||||
"link_url": "/about/",
|
||||
"order": 5,
|
||||
"order": 6,
|
||||
"items": [],
|
||||
},
|
||||
{
|
||||
"section_type": HomepageSection.TYPE_SUPPORTERS,
|
||||
"badge": "Acknowledgements",
|
||||
"title": "Our Supporters",
|
||||
"description": "Radiuma is made possible by the support of leading research institutions and organizations.",
|
||||
"order": 6,
|
||||
"badge": "Who It Serves",
|
||||
"title": "Built for Collaborative Teams",
|
||||
"description": "com2care demo workflows are organized around the people who review, analyze, and communicate medical-imaging evidence.",
|
||||
"order": 7,
|
||||
"items": [
|
||||
{
|
||||
"title": "University of British Columbia",
|
||||
"content": "Faculty of Medicine and the Department of Integrative Oncology.",
|
||||
"title": "Imaging Researchers",
|
||||
"content": "Build standardized pipelines and retain the context behind each processing decision.",
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
"title": "BC Cancer Research Institute",
|
||||
"content": "Supporting cutting-edge radiomics and medical imaging research in Vancouver, BC.",
|
||||
"title": "Clinical Research Teams",
|
||||
"content": "Review images and quantitative results together without presenting research output as diagnosis.",
|
||||
"order": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
ABOUT_SECTIONS = [
|
||||
{
|
||||
"section_type": AboutSection.TYPE_HERO,
|
||||
"badge": "Our Mission",
|
||||
"title": "Better imaging conversations, clearer research decisions",
|
||||
"subtitle": "Communication to Care",
|
||||
"content": (
|
||||
"com2care is built around a simple idea: complex medical-imaging evidence becomes "
|
||||
"more useful when researchers, clinicians, engineers, and data teams can examine it "
|
||||
"together in a shared, reproducible workflow."
|
||||
),
|
||||
"order": 1,
|
||||
"items": [
|
||||
{
|
||||
"title": "A collaborative care and research team",
|
||||
"image_alt": "Healthcare and imaging researchers reviewing medical images together",
|
||||
"static_image": "com2care-care-team.jpg",
|
||||
"is_featured": True,
|
||||
"order": 1,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"section_type": AboutSection.TYPE_GRID,
|
||||
"badge": "How We Work",
|
||||
"title": "Designed for shared understanding",
|
||||
"content": "Every part of the platform supports transparent, repeatable research communication.",
|
||||
"order": 2,
|
||||
"items": [
|
||||
{
|
||||
"icon": "01",
|
||||
"title": "Clinical context",
|
||||
"content": "Keep imaging evidence and analysis choices visible to the whole team.",
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
"icon": "02",
|
||||
"title": "Reproducible workflows",
|
||||
"content": "Save processing steps so collaborators can review and repeat the same pipeline.",
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
"icon": "03",
|
||||
"title": "Responsible research",
|
||||
"content": "Separate research exploration from clinical diagnosis and protect patient privacy.",
|
||||
"order": 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"section_type": AboutSection.TYPE_VIDEO,
|
||||
"badge": "Practical Learning",
|
||||
"title": "Viewing DICOM studies with an open medical-imaging workflow",
|
||||
"content": (
|
||||
"This independent tutorial demonstrates how researchers can import and inspect DICOM "
|
||||
"studies in 3D Slicer—skills that complement the workflows presented on com2care."
|
||||
),
|
||||
"video_url": "https://www.youtube.com/watch?v=EV8tAjAHeac",
|
||||
"video_styled_background": True,
|
||||
"video_size": "lg",
|
||||
"order": 3,
|
||||
"items": [],
|
||||
},
|
||||
]
|
||||
|
||||
DOWNLOAD_ITEMS = [
|
||||
{
|
||||
"name": "Radiuma Desktop",
|
||||
"name": "com2care Desktop",
|
||||
"platform": "windows",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/radiuma/radiuma/releases/latest/download/Radiuma-Setup.exe",
|
||||
"description": "Windows 10 and above (64-bit). Installer package.",
|
||||
"download_url": "https://com2care.com/downloads/",
|
||||
"description": "Demonstration release channel for Windows 10 and above (64-bit).",
|
||||
"is_active": True,
|
||||
"order": 1,
|
||||
},
|
||||
{
|
||||
"name": "Radiuma Desktop",
|
||||
"name": "com2care Desktop",
|
||||
"platform": "macos",
|
||||
"version": "Coming Soon",
|
||||
"download_url": "#",
|
||||
@@ -363,7 +467,7 @@ DOWNLOAD_ITEMS = [
|
||||
"order": 2,
|
||||
},
|
||||
{
|
||||
"name": "Radiuma Desktop",
|
||||
"name": "com2care Desktop",
|
||||
"platform": "linux",
|
||||
"version": "Coming Soon",
|
||||
"download_url": "#",
|
||||
@@ -375,19 +479,43 @@ DOWNLOAD_ITEMS = [
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Seed the database with initial Radiuma / Radiuma content from radiuma.com"
|
||||
help = "Seed a complete com2care demonstration site."
|
||||
|
||||
CONTENT_MODELS = (
|
||||
MainProduct,
|
||||
FAQEntry,
|
||||
DownloadItem,
|
||||
HeroSection,
|
||||
HomepageSection,
|
||||
AboutSection,
|
||||
PageVideo,
|
||||
ProductVideo,
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--flush",
|
||||
action="store_true",
|
||||
help="Delete all existing seed data before re-seeding",
|
||||
help="Delete existing public content before re-seeding.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--if-empty",
|
||||
action="store_true",
|
||||
help="Seed only when every public content table is empty.",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
if options["if_empty"] and not options["flush"] and self._content_exists():
|
||||
self.stdout.write("Public content already exists; demo seed skipped.")
|
||||
return
|
||||
|
||||
if options["flush"]:
|
||||
self.stdout.write("Flushing existing seed data...")
|
||||
self.stdout.write("Flushing existing public content...")
|
||||
ProductVideo.objects.all().delete()
|
||||
PageVideo.objects.all().delete()
|
||||
AboutSectionItem.objects.all().delete()
|
||||
AboutSection.objects.all().delete()
|
||||
ArticleSection.objects.all().delete()
|
||||
Article.objects.all().delete()
|
||||
SubProduct.objects.all().delete()
|
||||
@@ -398,66 +526,96 @@ class Command(BaseCommand):
|
||||
HomepageSection.objects.all().delete()
|
||||
HeroSection.objects.all().delete()
|
||||
|
||||
self._seed_products()
|
||||
main_product = self._seed_products()
|
||||
self._seed_faq()
|
||||
self._seed_downloads()
|
||||
self._seed_homepage_sections()
|
||||
self._seed_about_sections()
|
||||
self._seed_product_videos(main_product)
|
||||
self._seed_hero()
|
||||
self._seed_site_branding()
|
||||
self._seed_site_contact()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Content seeded successfully."))
|
||||
self.stdout.write(self.style.SUCCESS("com2care demonstration content seeded."))
|
||||
|
||||
def _content_exists(self):
|
||||
return any(model.objects.exists() for model in self.CONTENT_MODELS)
|
||||
|
||||
@staticmethod
|
||||
def _update_instance(instance, values):
|
||||
for field, value in values.items():
|
||||
setattr(instance, field, value)
|
||||
instance.save()
|
||||
|
||||
@staticmethod
|
||||
def _attach_static_image(instance, field_name, image_name):
|
||||
if not image_name or getattr(instance, field_name):
|
||||
return
|
||||
source = settings.BASE_DIR / "static" / "images" / image_name
|
||||
if not source.exists():
|
||||
return
|
||||
with source.open("rb") as handle:
|
||||
getattr(instance, field_name).save(source.name, File(handle), save=True)
|
||||
|
||||
def _seed_products(self):
|
||||
seeded_main_product = None
|
||||
for product_data in MAIN_PRODUCTS:
|
||||
sub_products_data = product_data.pop("sub_products")
|
||||
sub_products_data = product_data["sub_products"]
|
||||
product_defaults = {
|
||||
key: value for key, value in product_data.items() if key != "sub_products"
|
||||
}
|
||||
main_product, created = MainProduct.objects.get_or_create(
|
||||
slug=product_data["slug"],
|
||||
defaults=product_data,
|
||||
slug=product_defaults["slug"],
|
||||
defaults=product_defaults,
|
||||
)
|
||||
if not created:
|
||||
for field, value in product_data.items():
|
||||
setattr(main_product, field, value)
|
||||
main_product.save()
|
||||
self._update_instance(main_product, product_defaults)
|
||||
seeded_main_product = main_product
|
||||
|
||||
action = "Created" if created else "Updated"
|
||||
self.stdout.write(f" {action} main product: {main_product.name}")
|
||||
|
||||
for sub_data in sub_products_data:
|
||||
articles_data = sub_data.pop("articles")
|
||||
articles_data = sub_data["articles"]
|
||||
sub_defaults = {
|
||||
key: value for key, value in sub_data.items() if key != "articles"
|
||||
}
|
||||
sub_product, sub_created = SubProduct.objects.get_or_create(
|
||||
main_product=main_product,
|
||||
slug=sub_data["slug"],
|
||||
defaults=sub_data,
|
||||
slug=sub_defaults["slug"],
|
||||
defaults=sub_defaults,
|
||||
)
|
||||
if not sub_created:
|
||||
for field, value in sub_data.items():
|
||||
setattr(sub_product, field, value)
|
||||
sub_product.save()
|
||||
self._update_instance(sub_product, sub_defaults)
|
||||
|
||||
sub_action = "Created" if sub_created else "Updated"
|
||||
self.stdout.write(f" {sub_action} sub-product: {sub_product.name}")
|
||||
|
||||
for article_data in articles_data:
|
||||
sections_data = article_data.pop("sections")
|
||||
sections_data = article_data["sections"]
|
||||
article_defaults = {
|
||||
key: value for key, value in article_data.items() if key != "sections"
|
||||
}
|
||||
article, art_created = Article.objects.get_or_create(
|
||||
sub_product=sub_product,
|
||||
title=article_data["title"],
|
||||
defaults=article_data,
|
||||
title=article_defaults["title"],
|
||||
defaults=article_defaults,
|
||||
)
|
||||
if not art_created:
|
||||
for field, value in article_data.items():
|
||||
setattr(article, field, value)
|
||||
article.save()
|
||||
self._update_instance(article, article_defaults)
|
||||
|
||||
art_action = "Created" if art_created else "Updated"
|
||||
self.stdout.write(f" {art_action} article: {article.title}")
|
||||
|
||||
for section_data in sections_data:
|
||||
section, _ = ArticleSection.objects.get_or_create(
|
||||
article_section, section_created = ArticleSection.objects.get_or_create(
|
||||
article=article,
|
||||
title=section_data["title"],
|
||||
defaults=section_data,
|
||||
)
|
||||
if not section_created:
|
||||
self._update_instance(article_section, section_data)
|
||||
return seeded_main_product
|
||||
|
||||
def _seed_faq(self):
|
||||
for entry_data in FAQ_ENTRIES:
|
||||
@@ -466,34 +624,92 @@ class Command(BaseCommand):
|
||||
defaults=entry_data,
|
||||
)
|
||||
if not created:
|
||||
for field, value in entry_data.items():
|
||||
setattr(faq, field, value)
|
||||
faq.save()
|
||||
self._update_instance(faq, entry_data)
|
||||
|
||||
action = "Created" if created else "Updated"
|
||||
self.stdout.write(f" {action} FAQ: {faq.question[:60]}...")
|
||||
|
||||
def _seed_homepage_sections(self):
|
||||
for section_data in HOMEPAGE_SECTIONS:
|
||||
items_data = section_data.pop("items")
|
||||
items_data = section_data["items"]
|
||||
section_defaults = {
|
||||
key: value for key, value in section_data.items() if key != "items"
|
||||
}
|
||||
section, created = HomepageSection.objects.get_or_create(
|
||||
section_type=section_data["section_type"],
|
||||
defaults=section_data,
|
||||
section_type=section_defaults["section_type"],
|
||||
defaults=section_defaults,
|
||||
)
|
||||
if not created:
|
||||
for field, value in section_data.items():
|
||||
setattr(section, field, value)
|
||||
section.save()
|
||||
self._update_instance(section, section_defaults)
|
||||
|
||||
action = "Created" if created else "Updated"
|
||||
self.stdout.write(f" {action} homepage section: {section}")
|
||||
|
||||
for item_data in items_data:
|
||||
item, _ = HomepageSectionItem.objects.get_or_create(
|
||||
image_name = item_data.get("static_image", "")
|
||||
item_defaults = {
|
||||
key: value for key, value in item_data.items() if key != "static_image"
|
||||
}
|
||||
item, item_created = HomepageSectionItem.objects.get_or_create(
|
||||
section=section,
|
||||
title=item_data["title"],
|
||||
defaults=item_data,
|
||||
title=item_defaults["title"],
|
||||
defaults=item_defaults,
|
||||
)
|
||||
if not item_created:
|
||||
self._update_instance(item, item_defaults)
|
||||
self._attach_static_image(item, "image", image_name)
|
||||
|
||||
def _seed_about_sections(self):
|
||||
for section_data in ABOUT_SECTIONS:
|
||||
items_data = section_data["items"]
|
||||
section_defaults = {
|
||||
key: value for key, value in section_data.items() if key != "items"
|
||||
}
|
||||
section, created = AboutSection.objects.get_or_create(
|
||||
section_type=section_defaults["section_type"],
|
||||
title=section_defaults["title"],
|
||||
defaults=section_defaults,
|
||||
)
|
||||
if not created:
|
||||
self._update_instance(section, section_defaults)
|
||||
|
||||
for item_data in items_data:
|
||||
image_name = item_data.get("static_image", "")
|
||||
item_defaults = {
|
||||
key: value for key, value in item_data.items() if key != "static_image"
|
||||
}
|
||||
item, item_created = AboutSectionItem.objects.get_or_create(
|
||||
section=section,
|
||||
title=item_defaults["title"],
|
||||
defaults=item_defaults,
|
||||
)
|
||||
if not item_created:
|
||||
self._update_instance(item, item_defaults)
|
||||
self._attach_static_image(item, "image", image_name)
|
||||
|
||||
def _seed_product_videos(self, main_product):
|
||||
if not main_product:
|
||||
return
|
||||
data = {
|
||||
"badge": "Workflow Tutorial",
|
||||
"title": "Segmentation workflow from image to 3D review",
|
||||
"description": (
|
||||
"An independent demonstration of a guided medical-image segmentation workflow "
|
||||
"using open research tooling."
|
||||
),
|
||||
"video_url": "https://www.youtube.com/watch?v=_7oZygGp2ds",
|
||||
"video_styled_background": True,
|
||||
"video_size": "lg",
|
||||
"order": 1,
|
||||
"is_active": True,
|
||||
}
|
||||
video, created = ProductVideo.objects.get_or_create(
|
||||
main_product=main_product,
|
||||
title=data["title"],
|
||||
defaults=data,
|
||||
)
|
||||
if not created:
|
||||
self._update_instance(video, data)
|
||||
|
||||
def _seed_downloads(self):
|
||||
for item_data in DOWNLOAD_ITEMS:
|
||||
@@ -503,73 +719,52 @@ class Command(BaseCommand):
|
||||
defaults=item_data,
|
||||
)
|
||||
if not created:
|
||||
for field, value in item_data.items():
|
||||
setattr(item, field, value)
|
||||
item.save()
|
||||
self._update_instance(item, item_data)
|
||||
|
||||
action = "Created" if created else "Updated"
|
||||
self.stdout.write(f" {action} download: {item}")
|
||||
|
||||
def _seed_hero(self):
|
||||
data = {
|
||||
"badge": "Developing since 2021",
|
||||
"title": "Radiuma,",
|
||||
"title_highlight": "A Powerful Workflow Generator",
|
||||
"subtitle": "for Standardized Radiomics Analysis and Medical Image Visualization",
|
||||
"badge": "Communication-first medical imaging",
|
||||
"title": "Communication to Care,",
|
||||
"title_highlight": "From Images to Shared Understanding",
|
||||
"subtitle": "Collaborative medical imaging, radiomics, and reproducible research workflows",
|
||||
"description": (
|
||||
"Radiuma is a free, open-source software specialized for visualization, processing, "
|
||||
"segmentation, registration, fusion and analysis of medical and biomedical images, "
|
||||
"including radiomics and machine learning analysis."
|
||||
"com2care helps multidisciplinary teams explore complex imaging evidence, "
|
||||
"document analysis choices, and communicate results with clearer context."
|
||||
),
|
||||
"primary_cta_text": "Get Radiuma",
|
||||
"primary_cta_text": "Explore com2care",
|
||||
"primary_cta_url": "/products/",
|
||||
"secondary_cta_text": "About Radiuma",
|
||||
"secondary_cta_text": "Our Mission",
|
||||
"secondary_cta_url": "/about/",
|
||||
"image_alt": "Radiuma application — main workflow view",
|
||||
"image_alt": "A multidisciplinary care team collaborating around medical imaging",
|
||||
}
|
||||
hero = HeroSection.objects.first()
|
||||
if hero is None:
|
||||
HeroSection.objects.create(**data)
|
||||
self.stdout.write(" Created hero section")
|
||||
hero = HeroSection.objects.create(**data)
|
||||
else:
|
||||
for field, value in data.items():
|
||||
if field == "image":
|
||||
continue
|
||||
setattr(hero, field, value)
|
||||
hero.save()
|
||||
self.stdout.write(" Updated hero section")
|
||||
self._update_instance(hero, data)
|
||||
self._attach_static_image(hero, "image", "com2care-care-team.jpg")
|
||||
|
||||
def _seed_site_branding(self):
|
||||
branding = SiteBranding.load()
|
||||
branding.icon_alt = "com2care"
|
||||
branding.hero_logo_alt = "Communication to Care"
|
||||
branding.save(update_fields=["icon_alt", "hero_logo_alt"])
|
||||
|
||||
def _seed_site_contact(self):
|
||||
contact, created = SiteContact.objects.get_or_create(
|
||||
pk=1,
|
||||
defaults={
|
||||
"support_email": "support@radiuma.com",
|
||||
"discord_url": "https://discord.gg/9XxA6pV9hb",
|
||||
"email_card_description": "For direct software support:",
|
||||
"discord_card_description": "Join for community support and announcements.",
|
||||
"office_address": (
|
||||
"BC Cancer Research Center\n"
|
||||
"675 West 10th Ave, Office 6-112\n"
|
||||
"Vancouver, BC, V5Z 1L3\n"
|
||||
"Canada"
|
||||
),
|
||||
},
|
||||
)
|
||||
data = {
|
||||
"support_email": "support@com2care.com",
|
||||
"discord_url": "",
|
||||
"discord_label": "",
|
||||
"email_card_title": "Email com2care Support",
|
||||
"email_card_description": "For product, evaluation, and research questions:",
|
||||
"discord_card_title": "",
|
||||
"discord_card_description": "",
|
||||
"office_card_title": "",
|
||||
"office_address": "",
|
||||
}
|
||||
contact, created = SiteContact.objects.get_or_create(pk=1, defaults=data)
|
||||
if not created:
|
||||
updates = {
|
||||
"support_email": "support@radiuma.com",
|
||||
"discord_url": "https://discord.gg/9XxA6pV9hb",
|
||||
"email_card_description": "For direct software support:",
|
||||
"discord_card_description": "Join for community support and announcements.",
|
||||
"office_address": (
|
||||
"BC Cancer Research Center\n"
|
||||
"675 West 10th Ave, Office 6-112\n"
|
||||
"Vancouver, BC, V5Z 1L3\n"
|
||||
"Canada"
|
||||
),
|
||||
}
|
||||
for field, value in updates.items():
|
||||
setattr(contact, field, value)
|
||||
contact.save()
|
||||
action = "Created" if created else "Updated"
|
||||
self.stdout.write(f" {action} site contact")
|
||||
self._update_instance(contact, data)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.2.8 on 2026-08-01 15:06
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0003_sitebranding_hero_logo_sitebranding_hero_logo_alt_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sitebranding',
|
||||
name='hero_logo_alt',
|
||||
field=models.CharField(blank=True, default='Communication to Care', help_text='Accessible description for the homepage hero logo.', max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sitebranding',
|
||||
name='website_icon',
|
||||
field=models.FileField(blank=True, help_text='Icon shown in browser tabs and bookmarks. Use a square ICO, PNG, SVG, JPG, or WebP file. Leave empty to use the default Communication to Care icons.', null=True, upload_to='branding/icons/', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=('ico', 'png', 'svg', 'jpg', 'jpeg', 'webp'))]),
|
||||
),
|
||||
]
|
||||
+2
-2
@@ -33,7 +33,7 @@ class SiteBranding(models.Model):
|
||||
],
|
||||
help_text=(
|
||||
"Icon shown in browser tabs and bookmarks. Use a square ICO, PNG, SVG, "
|
||||
"JPG, or WebP file. Leave empty to use the default Radiuma icons."
|
||||
"JPG, or WebP file. Leave empty to use the default Communication to Care icons."
|
||||
),
|
||||
)
|
||||
hero_logo = models.ImageField(
|
||||
@@ -48,7 +48,7 @@ class SiteBranding(models.Model):
|
||||
hero_logo_alt = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
default="Radiuma",
|
||||
default="Communication to Care",
|
||||
help_text="Accessible description for the homepage hero logo.",
|
||||
)
|
||||
navbar_icon_size = models.PositiveSmallIntegerField(
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import os
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
from apps.core.models import SiteContact
|
||||
from apps.pages.models import AboutSection, HeroSection, HomepageSection
|
||||
from apps.products.models import MainProduct, ProductVideo
|
||||
|
||||
|
||||
class DemoSeedCommandTests(TestCase):
|
||||
def setUp(self):
|
||||
self.media_directory = TemporaryDirectory()
|
||||
self.addCleanup(self.media_directory.cleanup)
|
||||
|
||||
def _seed_if_empty(self):
|
||||
with self.settings(MEDIA_ROOT=self.media_directory.name):
|
||||
call_command("seed_content", "--if-empty", verbosity=0)
|
||||
|
||||
def test_seed_populates_complete_demo_when_public_content_is_empty(self):
|
||||
self._seed_if_empty()
|
||||
|
||||
self.assertTrue(MainProduct.objects.filter(slug="com2care").exists())
|
||||
self.assertTrue(HeroSection.objects.exclude(image="").exists())
|
||||
self.assertTrue(HomepageSection.objects.filter(section_type="video").exists())
|
||||
self.assertTrue(AboutSection.objects.filter(section_type="video").exists())
|
||||
self.assertTrue(ProductVideo.objects.filter(video_url__contains="youtube.com").exists())
|
||||
self.assertEqual(SiteContact.load().support_email, "support@com2care.com")
|
||||
|
||||
def test_if_empty_preserves_existing_admin_content(self):
|
||||
self._seed_if_empty()
|
||||
hero = HeroSection.objects.get()
|
||||
hero.title = "Admin-authored headline"
|
||||
hero.save(update_fields=["title"])
|
||||
|
||||
self._seed_if_empty()
|
||||
|
||||
hero.refresh_from_db()
|
||||
self.assertEqual(hero.title, "Admin-authored headline")
|
||||
|
||||
|
||||
class EnsureSuperuserCommandTests(TestCase):
|
||||
def test_creates_configurable_first_admin(self):
|
||||
environment = {
|
||||
"DJANGO_SUPERUSER_ENABLED": "True",
|
||||
"DJANGO_SUPERUSER_USERNAME": "siteadmin",
|
||||
"DJANGO_SUPERUSER_EMAIL": "siteadmin@com2care.com",
|
||||
"DJANGO_SUPERUSER_PASSWORD": "configurable-test-password",
|
||||
"DJANGO_SUPERUSER_SYNC_PASSWORD": "False",
|
||||
}
|
||||
with patch.dict(os.environ, environment, clear=False):
|
||||
call_command("ensure_superuser", verbosity=0)
|
||||
|
||||
user = get_user_model().objects.get(username="siteadmin")
|
||||
self.assertTrue(user.is_superuser)
|
||||
self.assertTrue(user.check_password("configurable-test-password"))
|
||||
|
||||
def test_restart_preserves_password_changed_in_admin(self):
|
||||
user = get_user_model().objects.create_superuser(
|
||||
username="admin",
|
||||
email="admin@com2care.com",
|
||||
password="changed-in-admin",
|
||||
)
|
||||
environment = {
|
||||
"DJANGO_SUPERUSER_USERNAME": "admin",
|
||||
"DJANGO_SUPERUSER_EMAIL": "admin@com2care.com",
|
||||
"DJANGO_SUPERUSER_PASSWORD": "environment-password",
|
||||
"DJANGO_SUPERUSER_SYNC_PASSWORD": "False",
|
||||
}
|
||||
with patch.dict(os.environ, environment, clear=False):
|
||||
call_command("ensure_superuser", verbosity=0)
|
||||
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.check_password("changed-in-admin"))
|
||||
|
||||
def test_server_operator_can_explicitly_rotate_password(self):
|
||||
user = get_user_model().objects.create_superuser(
|
||||
username="admin",
|
||||
email="old@com2care.com",
|
||||
password="old-password",
|
||||
)
|
||||
environment = {
|
||||
"DJANGO_SUPERUSER_USERNAME": "admin",
|
||||
"DJANGO_SUPERUSER_EMAIL": "new@com2care.com",
|
||||
"DJANGO_SUPERUSER_PASSWORD": "rotated-password",
|
||||
"DJANGO_SUPERUSER_SYNC_PASSWORD": "True",
|
||||
}
|
||||
with patch.dict(os.environ, environment, clear=False):
|
||||
call_command("ensure_superuser", verbosity=0)
|
||||
|
||||
user.refresh_from_db()
|
||||
self.assertEqual(user.email, "new@com2care.com")
|
||||
self.assertTrue(user.check_password("rotated-password"))
|
||||
|
||||
|
||||
class NormalizeBrandCommandTests(TestCase):
|
||||
def test_normalizes_existing_public_content_urls_and_slugs(self):
|
||||
old_name = "".join(("Radi", "uma"))
|
||||
old_slug = old_name.lower()
|
||||
product = MainProduct.objects.create(
|
||||
name=old_name,
|
||||
slug=old_slug,
|
||||
short_description=f"A workflow from {old_name}",
|
||||
description=f"Learn more at {old_slug}.com.",
|
||||
)
|
||||
contact = SiteContact.load()
|
||||
contact.support_email = f"support@{old_slug}.com"
|
||||
contact.save(update_fields=["support_email"])
|
||||
|
||||
call_command("normalize_brand", verbosity=0)
|
||||
|
||||
product.refresh_from_db()
|
||||
contact.refresh_from_db()
|
||||
self.assertEqual(product.name, "Communication to Care")
|
||||
self.assertEqual(product.slug, "com2care")
|
||||
self.assertEqual(product.description, "Learn more at com2care.com.")
|
||||
self.assertEqual(contact.support_email, "support@com2care.com")
|
||||
@@ -44,7 +44,7 @@ class SiteBrandingTemplateTests(TestCase):
|
||||
branding.icon = "branding/navigation-logo.png"
|
||||
branding.website_icon = "branding/icons/site-icon.png"
|
||||
branding.hero_logo = "branding/hero/hero-logo.png"
|
||||
branding.hero_logo_alt = "Radiuma research platform"
|
||||
branding.hero_logo_alt = "Communication to Care research platform"
|
||||
branding.save()
|
||||
|
||||
response = self.client.get(reverse("pages:home"))
|
||||
@@ -52,10 +52,10 @@ class SiteBrandingTemplateTests(TestCase):
|
||||
self.assertContains(response, 'src="/media/branding/navigation-logo.png"')
|
||||
self.assertContains(response, 'href="/media/branding/icons/site-icon.png"')
|
||||
self.assertContains(response, 'src="/media/branding/hero/hero-logo.png"')
|
||||
self.assertContains(response, 'alt="Radiuma research platform"')
|
||||
self.assertContains(response, 'alt="Communication to Care research platform"')
|
||||
|
||||
def test_default_assets_remain_when_custom_assets_are_empty(self):
|
||||
response = self.client.get(reverse("pages:home"))
|
||||
|
||||
self.assertContains(response, "images/favicon-16.png")
|
||||
self.assertContains(response, "images/screenshot-1.jpg")
|
||||
self.assertContains(response, "images/com2care-mark.svg")
|
||||
self.assertContains(response, "images/com2care-care-team.jpg")
|
||||
|
||||
Reference in New Issue
Block a user