fix: separate logo and hero icon

This commit is contained in:
mohamad
2026-08-03 16:29:25 +03:30
parent 04b6e9049e
commit 4fc4282f0f
45 changed files with 900 additions and 495 deletions
+9 -8
View File
@@ -1,17 +1,18 @@
DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_SECRET_KEY=uqgb2wi9@1dr8alhhx$rp_tx!%_en$k7w6yjbu7wz-qr3$&3-w
DJANGO_SECRET_KEY=replace-with-a-long-random-production-secret
DEBUG=False
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0,radiuma.com,www.radiuma.com
CSRF_TRUSTED_ORIGINS=localhost,127.0.0.1,0.0.0.0,radiuma.com,www.radiuma.com
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0,com2care.com,www.com2care.com
CSRF_TRUSTED_ORIGINS=http://localhost:8000,https://com2care.com,https://www.com2care.com
POSTGRES_DB=radiuma
POSTGRES_USER=radiuma_user
POSTGRES_PASSWORD=eS4_WYJH97gywyoHjP6v
POSTGRES_DB=com2care
POSTGRES_USER=com2care_user
POSTGRES_PASSWORD=replace-with-a-strong-database-password
POSTGRES_HOST=db
POSTGRES_PORT=5432
SECURE_SSL_REDIRECT=False
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@yourdomain.com
DJANGO_SUPERUSER_PASSWORD=bS7_U_uRTmivj7W-lCR5
DJANGO_SUPERUSER_EMAIL=admin@com2care.com
DJANGO_SUPERUSER_PASSWORD=cmosV6Tw46Odv7UN
DJANGO_SUPERUSER_SYNC_PASSWORD=False
+4
View File
@@ -49,3 +49,7 @@ media/
private_uploads/
staticfiles/
*.DS_Store
test_media_releases/
docs/
scripts/
videos/
+125 -231
View File
@@ -1,278 +1,172 @@
# Radiuma Website
# Communication to Care
Django MVT informational website for **Radiuma**, showcasing the **Radiuma** medical imaging and radiomics software suite.
The Django website for **Communication to Care**, published at **com2care.com**. It presents the
com2care medical-imaging research platform, product modules, learning videos, downloads, FAQs,
and support information.
## Tech Stack
## Start with Docker
| Layer | Technology |
|---|---|
| Backend | Django 5.1 (MVT) |
| Database | PostgreSQL 16 |
| Static files | WhiteNoise (with Brotli compression) |
| Application server | Gunicorn |
| Containerization | Docker + Docker Compose |
| Frontend | Vanilla HTML/CSS/JS (no framework) |
## Project Structure
```
tecvico_website/
├── config/ # Django project configuration
│ └── settings/
│ ├── base.py # Shared settings
│ ├── development.py # Dev settings (DEBUG=True, dotenv)
│ └── production.py # Production settings (security headers)
├── apps/
│ ├── core/ # Context processors, management commands
│ │ └── management/commands/seed_content.py
│ ├── products/ # MainProduct, SubProduct, Article, ArticleSection
│ └── pages/ # FAQEntry, DownloadItem; static pages
├── templates/ # Global templates
│ ├── base.html
│ ├── partials/
│ └── pages/ & products/
├── static/
│ ├── css/main.css # Full design system (dark glass/ice theme)
│ └── js/main.js # Navbar, FAQ accordion, scroll effects
├── Dockerfile
├── docker-compose.yml # Production compose
├── docker-compose.override.yml # Development compose overrides
└── entrypoint.sh # DB wait + migrate on container start
```
## URL Map
| URL | View | Description |
|---|---|---|
| `/` | `HomeView` | Landing page |
| `/about/` | `AboutView` | What is Radiuma |
| `/products/` | `ProductOverviewView` | All main products |
| `/products/<main-slug>/` | `MainProductDetailView` | Main product + sub-products |
| `/products/<main-slug>/<sub-slug>/` | `SubProductDetailView` | Sub-product + articles |
| `/downloads/` | `DownloadsView` | Download items by platform |
| `/faq/` | `FAQView` | FAQ entries |
| `/contact/` | `ContactView` | Contact info |
| `/admin/` | Django Admin | Admin panel |
## Data Model
```
MainProduct
└── SubProduct (FK → MainProduct)
└── Article (FK → SubProduct)
└── ArticleSection (FK → Article) ← key/value metadata
FAQEntry ← admin-managed FAQ items
DownloadItem ← admin-managed download links per platform
```
---
## Local Development (with Docker)
### Prerequisites
- Docker Desktop (or Docker Engine + Compose plugin)
### 1. Clone & configure
Docker Compose includes working local defaults, database migrations, demo content, and the first
admin account. No setup command or `.env` file is required for a local demonstration.
```bash
docker compose up -d --build
```
Open:
- Website: <http://localhost:8000>
- Admin: <http://localhost:8000/admin/>
Initial local admin credentials:
```text
Username: admin
Password: cmosV6Tw46Odv7UN
```
Change the password immediately after the first login using **Django Admin → Change password**.
The startup process never resets an existing admin password unless credential synchronization is
explicitly enabled.
## Automatic startup behavior
Every web-container start performs these idempotent steps:
1. Wait for PostgreSQL.
2. Collect static files.
3. Apply Django migrations.
4. Normalize public database text and URLs to the Communication to Care identity.
5. Seed demonstration content only when all public content tables are empty.
6. Create the configured admin account only when it does not exist.
7. Start Django or Gunicorn.
Demo content includes:
- A com2care product with five medical-imaging modules.
- Articles, structured specifications, FAQs, and release examples.
- Homepage and About page sections with local images.
- A generated Communication to Care hero image.
- Relevant external YouTube tutorials for DICOM review and image segmentation.
- `support@com2care.com` contact data.
Content created or edited by an admin is preserved on later container restarts.
## Optional configuration
Copy `.env.example` to `.env` only when you want to override the local defaults:
```bash
git clone <repo-url> tecvico_website
cd tecvico_website
cp .env.example .env
```
Edit `.env` and set at minimum:
Important variables:
```
DJANGO_SECRET_KEY=your-very-secure-random-key
POSTGRES_PASSWORD=choose-a-strong-password
```
| Variable | Purpose |
|---|---|
| `DJANGO_SECRET_KEY` | Required strong secret for production |
| `ALLOWED_HOSTS` | Hostnames, including `com2care.com` |
| `CSRF_TRUSTED_ORIGINS` | Full trusted origins with `https://` |
| `POSTGRES_DB` | PostgreSQL database name |
| `POSTGRES_USER` | PostgreSQL user |
| `POSTGRES_PASSWORD` | PostgreSQL password |
| `DJANGO_SUPERUSER_ENABLED` | Set `False` to disable automatic admin creation |
| `DJANGO_SUPERUSER_USERNAME` | First admin username |
| `DJANGO_SUPERUSER_EMAIL` | First admin email |
| `DJANGO_SUPERUSER_PASSWORD` | First admin password |
| `DJANGO_SUPERUSER_SYNC_PASSWORD` | Set `True` for one restart to rotate an existing admin password from environment values |
### 2. Start services (development mode)
For a server-side password rotation, set the new `DJANGO_SUPERUSER_PASSWORD`, temporarily set
`DJANGO_SUPERUSER_SYNC_PASSWORD=True`, restart the web service once, then restore it to `False`.
An admin can always change their own password through Django Admin without changing server
configuration.
The `docker-compose.override.yml` automatically activates when you run `docker compose up`, mounting the source code and using the development settings.
## Production
Use strong values in `.env`, terminate TLS with a reverse proxy, and start only the production
Compose file:
```bash
docker compose up --build
docker compose -f docker-compose.yml up -d --build
```
The app is available at **http://localhost:8000**
At minimum, replace these local defaults in production:
### 3. Create a superuser
- `DJANGO_SECRET_KEY`
- `POSTGRES_PASSWORD`
- `DJANGO_SUPERUSER_PASSWORD`
- `SECURE_SSL_REDIRECT=True`
```bash
docker compose exec web python manage.py createsuperuser
```
The default production host configuration already includes `com2care.com` and
`www.com2care.com`.
### 4. Seed initial content
## Manual development
Populate the database with content scraped and adapted from visera.ca:
```bash
docker compose exec web python manage.py seed_content
```
To flush and re-seed from scratch:
```bash
docker compose exec web python manage.py seed_content --flush
```
---
## Local Development (without Docker)
### Prerequisites
Requirements:
- Python 3.12+
- PostgreSQL 14+
### 1. Set up virtual environment
```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
source .venv/bin/activate
pip install -r requirements-dev.txt
```
### 2. Configure environment
```bash
cp .env.example .env
```
Edit `.env`:
```
DJANGO_SECRET_KEY=your-key
POSTGRES_DB=tecvico
POSTGRES_USER=your_pg_user
POSTGRES_PASSWORD=your_pg_password
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
```
### 3. Create the database
```bash
createdb tecvico
```
### 4. Run migrations & seed
```bash
python manage.py migrate
python manage.py seed_content
python manage.py createsuperuser
```
### 5. Start development server
```bash
python manage.py seed_content --if-empty
python manage.py ensure_superuser
python manage.py runserver
```
---
## Management commands
## Running Tests
```bash
# Add demo data only to a completely empty public database
python manage.py seed_content --if-empty
### With Django test runner
# Intentionally replace all public content with the demo dataset
python manage.py seed_content --flush
# Normalize existing public text, slugs, URLs, and email addresses
python manage.py normalize_brand
# Create or optionally synchronize the configured admin
python manage.py ensure_superuser
```
## Tests
```bash
python manage.py test apps
```
### With pytest (requires `requirements-dev.txt`)
or:
```bash
pytest
```
### With coverage report
## Project structure
```bash
coverage run -m pytest
coverage report -m
coverage html # Generates htmlcov/index.html
```text
config/ Django settings and root URLs
apps/core/ Branding, contact settings, bootstrap commands
apps/pages/ Home, About, FAQ, Contact, custom pages, videos
apps/products/ Products, articles, releases, product videos
templates/ Django templates
static/css/main.css Site design system
static/images/ com2care brand and editorial assets
static/js/main.js Navigation and interaction behavior
docker-compose.yml PostgreSQL and production web service
docker-compose.override.yml Local development override
entrypoint.sh Automated database and content bootstrap
```
---
## Technology
## Production Deployment
### 1. Build and start production containers
Remove `docker-compose.override.yml` (or don't override it) and pass production environment variables:
```bash
DJANGO_SETTINGS_MODULE=config.settings.production \
docker compose -f docker-compose.yml up --build -d
```
### 2. Production `.env` checklist
| Variable | Notes |
|---|---|
| `DJANGO_SECRET_KEY` | Use `python -c "import secrets; print(secrets.token_urlsafe(50))"` |
| `DEBUG` | Must be `False` |
| `ALLOWED_HOSTS` | Comma-separated: `yourdomain.com,www.yourdomain.com` |
| `CSRF_TRUSTED_ORIGINS` | `https://yourdomain.com` |
| `POSTGRES_PASSWORD` | Strong random password |
| `SECURE_SSL_REDIRECT` | `True` when behind TLS termination |
### 3. Reverse proxy (recommended)
Place an Nginx or Caddy reverse proxy in front of Gunicorn for TLS termination and serving static files (or let WhiteNoise handle statics directly).
---
## Admin Panel
Access Django Admin at `/admin/` with superuser credentials.
### What you can manage
| Model | Description |
|---|---|
| **Main Products** | Top-level products with nested sub-products inline |
| **Sub Products** | Modules within a main product; articles editable inline |
| **Articles** | Article entries with section key/values inline |
| **Article Sections** | Individual key-value metadata rows |
| **FAQ Entries** | Accordion FAQ items (order, active toggle) |
| **Download Items** | Platform download links (Windows/macOS/Linux) |
---
## Seed Content
The `seed_content` command populates:
- **Radiuma** (MainProduct) with 5 sub-products:
- Image Processing
- Radiomics Features
- Medical Image Visualization
- Format Conversion
- Workflow Management
- Articles and sections for each sub-product
- 6 FAQ entries
- 3 download items (Windows active, macOS/Linux coming soon)
Content is adapted from the original [visera.ca](https://visera.ca) website.
---
## Design System
The website uses a custom dark-glass design combining:
- **visera.ca** aesthetic — dark background, organic blob animations, blue/teal accents
- **Apple visionOS Ice** aesthetic — frosted glass panels (`backdrop-filter: blur`), translucent cards, soft gradients
Key CSS custom properties are in `static/css/main.css` under `:root`.
---
## License
Content is adapted from visera.ca under CC BY-NC-SA. Software code is proprietary to Radiuma.
- Django
- PostgreSQL 16
- Gunicorn
- WhiteNoise
- Docker Compose
- Vanilla HTML, CSS, and JavaScript
+1 -1
View File
@@ -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.")
+354 -159
View File
@@ -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
View File
@@ -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(
+120
View File
@@ -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")
+4 -4
View File
@@ -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")
@@ -0,0 +1,18 @@
# Generated by Django 5.2.8 on 2026-08-01 15:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pages', '0017_video_description_format'),
]
operations = [
migrations.AlterField(
model_name='herosection',
name='title',
field=models.CharField(blank=True, help_text="Main title line (e.g. 'Communication to Care,').", max_length=300),
),
]
+1 -1
View File
@@ -23,7 +23,7 @@ RESERVED_PAGE_SLUGS = frozenset({
class HeroSection(models.Model):
badge = models.CharField(max_length=200, blank=True, help_text="Small label above the title (e.g. 'Developing since 2021').")
title = models.CharField(max_length=300, blank=True, help_text="Main title line (e.g. 'Radiuma,').")
title = models.CharField(max_length=300, blank=True, help_text="Main title line (e.g. 'Communication to Care,').")
title_highlight = models.CharField(max_length=300, blank=True, help_text="Second title line shown in gradient colour (e.g. 'A Powerful Workflow Generator').")
subtitle = models.CharField(max_length=500, blank=True, help_text="Short line below the title (e.g. 'for Standardized Radiomics Analysis…').")
description = models.TextField(blank=True, help_text="Longer paragraph below the subtitle.")
+3 -3
View File
@@ -156,8 +156,8 @@ class PageVideoTest(TestCase):
class ProductVideoTest(TestCase):
def setUp(self):
self.main_product = MainProduct.objects.create(
name="Radiuma",
slug="radiuma",
name="Communication to Care",
slug="com2care",
short_description="Short",
description="Long",
is_active=True,
@@ -173,7 +173,7 @@ class ProductVideoTest(TestCase):
is_active=True,
)
response = self.client.get(
reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
)
self.assertContains(response, "Product Demo")
self.assertContains(response, "video-block--full")
+1 -1
View File
@@ -207,7 +207,7 @@ class PageVideoArchiveView(ListView):
PAGE_CONFIG = {
PageVideo.PAGE_CONTACT: (
"Contact videos",
"Guides and updates from the Radiuma team.",
"Guides and updates from the Communication to Care team.",
"pages:contact",
),
PageVideo.PAGE_FAQ: (
+18 -18
View File
@@ -11,8 +11,8 @@ from apps.products.models import Article, ArticleSection, MainProduct, SubProduc
class ProductViewsSetup(TestCase):
def setUp(self):
self.main_product = MainProduct.objects.create(
name="Radiuma",
slug="radiuma",
name="Communication to Care",
slug="com2care",
short_description="Radiomics software",
description="Full description.",
)
@@ -61,17 +61,17 @@ class ProductOverviewViewTest(ProductViewsSetup):
class MainProductDetailViewTest(ProductViewsSetup):
def test_detail_returns_200(self):
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_detail_uses_correct_template(self):
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertTemplateUsed(response, "products/main_detail.html")
def test_detail_contains_product_in_context(self):
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertEqual(response.context["main_product"], self.main_product)
@@ -83,7 +83,7 @@ class MainProductDetailViewTest(ProductViewsSetup):
def test_inactive_product_returns_404(self):
self.main_product.is_active = False
self.main_product.save()
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
@@ -93,7 +93,7 @@ class MainProductDetailViewTest(ProductViewsSetup):
title="Overview",
description="Main product article.",
)
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertIn("articles", response.context)
self.assertIn(main_article, list(response.context["articles"]))
@@ -104,7 +104,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_returns_200(self):
url = reverse(
"products:sub_product_detail",
kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
kwargs={"main_slug": "com2care", "sub_slug": "image-processing"},
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
@@ -112,7 +112,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_uses_correct_template(self):
url = reverse(
"products:sub_product_detail",
kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
kwargs={"main_slug": "com2care", "sub_slug": "image-processing"},
)
response = self.client.get(url)
self.assertTemplateUsed(response, "products/sub_detail.html")
@@ -120,7 +120,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_context_keys(self):
url = reverse(
"products:sub_product_detail",
kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
kwargs={"main_slug": "com2care", "sub_slug": "image-processing"},
)
response = self.client.get(url)
self.assertIn("main_product", response.context)
@@ -131,7 +131,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_articles_in_context(self):
url = reverse(
"products:sub_product_detail",
kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
kwargs={"main_slug": "com2care", "sub_slug": "image-processing"},
)
response = self.client.get(url)
self.assertIn(self.article, list(response.context["articles"]))
@@ -139,7 +139,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_nonexistent_sub_slug_returns_404(self):
url = reverse(
"products:sub_product_detail",
kwargs={"main_slug": "radiuma", "sub_slug": "does-not-exist"},
kwargs={"main_slug": "com2care", "sub_slug": "does-not-exist"},
)
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
@@ -174,7 +174,7 @@ class SubProductOlderVersionsViewTest(ProductViewsSetup):
)
url = reverse(
"products:sub_product_versions",
kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
kwargs={"main_slug": "com2care", "sub_slug": "image-processing"},
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
@@ -198,7 +198,7 @@ class SubProductOlderVersionsViewTest(ProductViewsSetup):
)
url = reverse(
"products:main_product_versions",
kwargs={"main_slug": "radiuma"},
kwargs={"main_slug": "com2care"},
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
@@ -213,7 +213,7 @@ class SubProductOlderVersionsViewTest(ProductViewsSetup):
windows_download_url="https://example.com/win.exe",
is_active=True,
)
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertTrue(response.context["show_releases_section"])
@@ -236,7 +236,7 @@ class SubProductOlderVersionsViewTest(ProductViewsSetup):
windows_download_url="https://example.com/prev.exe",
is_active=True,
)
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertContains(response, "Beta")
self.assertContains(response, "Previous")
@@ -254,7 +254,7 @@ class SubProductOlderVersionsViewTest(ProductViewsSetup):
windows_download_url="https://example.com/ea.exe",
is_active=True,
)
url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(url)
self.assertContains(response, "Early Access")
self.assertNotContains(response, ">Stable<")
@@ -301,7 +301,7 @@ class ReleaseAssetDownloadViewTest(ProductViewsSetup):
windows_download_file=SimpleUploadedFile("win.tar.gz", b"gz"),
is_active=True,
)
detail_url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
detail_url = reverse("products:main_product_detail", kwargs={"main_slug": "com2care"})
response = self.client.get(detail_url)
download_url = reverse(
"products:release_asset_download",
+2 -2
View File
@@ -67,8 +67,8 @@ WSGI_APPLICATION = "config.wsgi.application"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("POSTGRES_DB", "tecvico"),
"USER": os.environ.get("POSTGRES_USER", "tecvico_user"),
"NAME": os.environ.get("POSTGRES_DB", "com2care"),
"USER": os.environ.get("POSTGRES_USER", "com2care_user"),
"PASSWORD": os.environ.get("POSTGRES_PASSWORD", ""),
"HOST": os.environ.get("POSTGRES_HOST", "localhost"),
"PORT": os.environ.get("POSTGRES_PORT", "5432"),
+3 -3
View File
@@ -5,9 +5,9 @@ from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
admin.site.site_header = "Radiuma Administration"
admin.site.site_title = "Radiuma Admin"
admin.site.index_title = "Welcome to Radiuma Administration"
admin.site.site_header = "Communication to Care Administration"
admin.site.site_title = "Communication to Care Admin"
admin.site.index_title = "Welcome to Communication to Care Administration"
urlpatterns = [
path("admin/", admin.site.urls),
+4
View File
@@ -1,4 +1,8 @@
services:
db:
ports:
- "5433:5432"
web:
build: .
environment:
+19 -9
View File
@@ -4,14 +4,12 @@ services:
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
env_file:
- .env
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-com2care}
POSTGRES_USER: ${POSTGRES_USER:-com2care_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-com2care_local_password}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-com2care_user} -d ${POSTGRES_DB:-com2care}"]
interval: 10s
timeout: 5s
retries: 5
@@ -21,11 +19,22 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- .env
environment:
DJANGO_SETTINGS_MODULE: config.settings.production
DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE:-config.settings.production}
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-django-insecure-local-com2care-change-in-production}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,0.0.0.0,com2care.com,www.com2care.com}
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS:-http://localhost:8000,https://com2care.com,https://www.com2care.com}
SECURE_SSL_REDIRECT: ${SECURE_SSL_REDIRECT:-False}
POSTGRES_DB: ${POSTGRES_DB:-com2care}
POSTGRES_USER: ${POSTGRES_USER:-com2care_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-com2care_local_password}
POSTGRES_HOST: db
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
DJANGO_SUPERUSER_ENABLED: ${DJANGO_SUPERUSER_ENABLED:-True}
DJANGO_SUPERUSER_USERNAME: ${DJANGO_SUPERUSER_USERNAME:-admin}
DJANGO_SUPERUSER_EMAIL: ${DJANGO_SUPERUSER_EMAIL:-admin@com2care.com}
DJANGO_SUPERUSER_PASSWORD: ${DJANGO_SUPERUSER_PASSWORD:-cmosV6Tw46Odv7UN}
DJANGO_SUPERUSER_SYNC_PASSWORD: ${DJANGO_SUPERUSER_SYNC_PASSWORD:-False}
volumes:
- ./media:/app/media
- ./staticfiles:/app/staticfiles
@@ -35,3 +44,4 @@ services:
volumes:
postgres_data:
media_data:
+9 -3
View File
@@ -6,8 +6,8 @@ until python -c "
import os, psycopg2, sys
try:
psycopg2.connect(
dbname=os.environ.get('POSTGRES_DB', 'tecvico'),
user=os.environ.get('POSTGRES_USER', 'tecvico_user'),
dbname=os.environ.get('POSTGRES_DB', 'com2care'),
user=os.environ.get('POSTGRES_USER', 'com2care_user'),
password=os.environ.get('POSTGRES_PASSWORD', ''),
host=os.environ.get('POSTGRES_HOST', 'db'),
port=os.environ.get('POSTGRES_PORT', '5432'),
@@ -28,7 +28,13 @@ python manage.py collectstatic --noinput
echo "Running database migrations..."
python manage.py migrate --noinput
echo "Creating superuser if not exists..."
echo "Normalizing the public brand..."
python manage.py normalize_brand
echo "Adding demonstration content when the database is empty..."
python manage.py seed_content --if-empty
echo "Ensuring the first admin account exists..."
python manage.py ensure_superuser
echo "Starting application..."
Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 88" role="img" aria-labelledby="title desc">
<title id="title">com2care</title>
<desc id="desc">Communication to Care logo</desc>
<defs>
<linearGradient id="logoBubbleA" x1="8" y1="8" x2="48" y2="52" gradientUnits="userSpaceOnUse">
<stop stop-color="#9b7cf6"/>
<stop offset="1" stop-color="#6d5ee7"/>
</linearGradient>
<linearGradient id="logoBubbleB" x1="22" y1="18" x2="58" y2="57" gradientUnits="userSpaceOnUse">
<stop stop-color="#44d6cc"/>
<stop offset="1" stop-color="#239fbd"/>
</linearGradient>
<linearGradient id="wordmark" x1="88" y1="18" x2="388" y2="72" gradientUnits="userSpaceOnUse">
<stop stop-color="#f4f1ff"/>
<stop offset=".55" stop-color="#c9c2f7"/>
<stop offset="1" stop-color="#73e0da"/>
</linearGradient>
</defs>
<g transform="translate(4 12)">
<path fill="url(#logoBubbleA)" d="M8 10.5A8.5 8.5 0 0 1 16.5 2h25A8.5 8.5 0 0 1 50 10.5v18a8.5 8.5 0 0 1-8.5 8.5H28.2L16 47.2V37.1a8.5 8.5 0 0 1-8-8.5z"/>
<path fill="url(#logoBubbleB)" d="M24 24.5a8.5 8.5 0 0 1 8.5-8.5h15a8.5 8.5 0 0 1 8.5 8.5v17a8.5 8.5 0 0 1-8.5 8.5H45v10l-12-10h-.5a8.5 8.5 0 0 1-8.5-8.5z"/>
<path fill="#fff" d="M40 41.9c-1.2-1-7.1-5.6-7.1-10.1 0-2.8 2.1-4.8 4.8-4.8 1.5 0 2.7.7 3.5 1.8A4.3 4.3 0 0 1 44.7 27c2.7 0 4.8 2 4.8 4.8 0 4.5-5.9 9.1-7.1 10.1l-1.2 1z"/>
</g>
<text x="84" y="60" fill="url(#wordmark)" font-family="Inter, Arial, sans-serif" font-size="50" font-weight="700" letter-spacing="-2">com2care</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title desc">
<title id="title">com2care mark</title>
<desc id="desc">Two connected conversation shapes forming a care heart</desc>
<defs>
<linearGradient id="bubbleA" x1="8" y1="8" x2="48" y2="52" gradientUnits="userSpaceOnUse">
<stop stop-color="#9b7cf6"/>
<stop offset="1" stop-color="#6d5ee7"/>
</linearGradient>
<linearGradient id="bubbleB" x1="22" y1="18" x2="58" y2="57" gradientUnits="userSpaceOnUse">
<stop stop-color="#44d6cc"/>
<stop offset="1" stop-color="#239fbd"/>
</linearGradient>
</defs>
<path fill="url(#bubbleA)" d="M8 10.5A8.5 8.5 0 0 1 16.5 2h25A8.5 8.5 0 0 1 50 10.5v18a8.5 8.5 0 0 1-8.5 8.5H28.2L16 47.2V37.1a8.5 8.5 0 0 1-8-8.5z"/>
<path fill="url(#bubbleB)" d="M24 24.5a8.5 8.5 0 0 1 8.5-8.5h15a8.5 8.5 0 0 1 8.5 8.5v17a8.5 8.5 0 0 1-8.5 8.5H45v10l-12-10h-.5a8.5 8.5 0 0 1-8.5-8.5z"/>
<path fill="#fff" d="M40 41.9c-1.2-1-7.1-5.6-7.1-10.1 0-2.8 2.1-4.8 4.8-4.8 1.5 0 2.7.7 3.5 1.8A4.3 4.3 0 0 1 44.7 27c2.7 0 4.8 2 4.8 4.8 0 4.5-5.9 9.1-7.1 10.1l-1.2 1z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

+8 -5
View File
@@ -4,15 +4,18 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="{% block meta_description %}Radiuma — Advanced Medical Imaging & Radiomics Solutions{% endblock %}" />
<title>{% block title %}Radiuma{% endblock %} | Radiuma</title>
<meta name="description" content="{% block meta_description %}Communication to Care — Advanced Medical Imaging & Radiomics Solutions{% endblock %}" />
<meta property="og:site_name" content="Communication to Care" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://com2care.com{% static 'images/com2care-care-team.jpg' %}" />
<link rel="canonical" href="https://com2care.com{{ request.path }}" />
<title>{% block title %}Communication to Care{% endblock %} | Communication to Care</title>
{% if site_branding.website_icon %}
<link rel="icon" href="{{ site_branding.website_icon.url }}" />
<link rel="apple-touch-icon" href="{{ site_branding.website_icon.url }}" />
{% else %}
<link rel="icon" href="{% static 'images/favicon-16.png' %}" type="image/png" sizes="16x16" />
<link rel="icon" href="{% static 'images/favicon-32.png' %}" type="image/png" sizes="32x32" />
<link rel="apple-touch-icon" href="{% static 'images/favicon-180.png' %}" sizes="180x180" />
<link rel="icon" href="{% static 'images/com2care-mark.svg' %}" type="image/svg+xml" />
<link rel="apple-touch-icon" href="{% static 'images/com2care-mark.svg' %}" />
{% endif %}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+1 -1
View File
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block title %}About{% endblock %}
{% block meta_description %}Learn about Radiuma — Visualized & Standardized Environment for Radiomics Analysis, developed at UBC and BC Cancer Research Institute.{% endblock %}
{% block meta_description %}Learn how Communication to Care helps multidisciplinary teams build clearer, reproducible medical-imaging research workflows.{% endblock %}
{% block content %}
{% include "partials/_page_sections.html" with sections=about_sections %}
+2 -2
View File
@@ -2,7 +2,7 @@
{% load static %}
{% block title %}Contact{% endblock %}
{% block meta_description %}Contact Radiuma — support for Radiuma software and general inquiries.{% endblock %}
{% block meta_description %}Contact Communication to Care for com2care product support, research evaluation, and general inquiries.{% endblock %}
{% block content %}
@@ -15,7 +15,7 @@
<div class="page-hero-content">
<div class="section-badge">Get in Touch</div>
<h1 class="page-hero-title" id="contact-heading">Contact Us</h1>
<p class="page-hero-subtitle">Have a question or want to reach the Radiuma team? Fill out the form or contact us directly below.</p>
<p class="page-hero-subtitle">Have a product, evaluation, or research question? Send the com2care team a message below.</p>
</div>
</div>
</section>
+1 -1
View File
@@ -2,7 +2,7 @@
{% load static %}
{% block title %}FAQ{% endblock %}
{% block meta_description %}Frequently asked questions about Radiuma by Radiuma — licensing, citation, system requirements, and more.{% endblock %}
{% block meta_description %}Frequently asked questions about Communication to Care licensing, research use, releases, support, and responsible deployment.{% endblock %}
{% block content %}
+4 -6
View File
@@ -2,7 +2,7 @@
{% load static %}
{% block title %}Home{% endblock %}
{% block meta_description %}Radiuma — A Powerful Workflow Generator for Standardized Radiomics Analysis and Medical Image Visualization.{% endblock %}
{% block meta_description %}Communication to Care — A Powerful Workflow Generator for Standardized Radiomics Analysis and Medical Image Visualization.{% endblock %}
{% block content %}
@@ -35,18 +35,16 @@
{% endif %}
{% endif %}
</div>
{% if site_branding.hero_logo or hero and hero.image or not hero %}
<div class="hero-app-preview fade-in">
<div class="hero-app-preview-glow" aria-hidden="true"></div>
{% if site_branding.hero_logo %}
<img src="{{ site_branding.hero_logo.url }}" alt="{{ site_branding.hero_logo_alt|default:'Radiuma' }}" loading="eager" />
<img src="{{ site_branding.hero_logo.url }}" alt="{{ site_branding.hero_logo_alt|default:'Communication to Care' }}" loading="eager" />
{% elif hero and hero.image %}
<img src="{{ hero.image.url }}" alt="{{ hero.image_alt }}" loading="eager" />
{% elif not hero %}
<img src="{% static 'images/screenshot-1.jpg' %}" alt="Radiuma application — main workflow view" loading="eager" />
{% else %}
<img src="{% static 'images/com2care-care-team.jpg' %}" alt="A multidisciplinary care team collaborating around medical imaging" loading="eager" />
{% endif %}
</div>
{% endif %}
</div>
</section>
+2 -2
View File
@@ -1,14 +1,14 @@
{% load static %}
{% if placement == "footer" %}
<img
{% if site_branding.icon %}src="{{ site_branding.icon.url }}"{% else %}src="{% static 'images/favicon-180.png' %}"{% endif %}
{% if site_branding.icon %}src="{{ site_branding.icon.url }}"{% else %}src="{% static 'images/com2care-mark.svg' %}"{% endif %}
{% if site_branding.icon_alt %}alt="{{ site_branding.icon_alt }}"{% else %}alt="" aria-hidden="true"{% endif %}
class="brand-icon"
style="{{ site_branding.footer_icon_style }}"
/>
{% else %}
<img
{% if site_branding.icon %}src="{{ site_branding.icon.url }}"{% else %}src="{% static 'images/favicon-180.png' %}"{% endif %}
{% if site_branding.icon %}src="{{ site_branding.icon.url }}"{% else %}src="{% static 'images/com2care-mark.svg' %}"{% endif %}
{% if site_branding.icon_alt %}alt="{{ site_branding.icon_alt }}"{% else %}alt="" aria-hidden="true"{% endif %}
class="brand-icon"
style="{{ site_branding.navbar_icon_style }}"
+7 -9
View File
@@ -8,12 +8,12 @@
<div class="footer-grid">
<div class="footer-brand-col">
<a href="{% url 'pages:home' %}" class="footer-logo" aria-label="Radiuma home">
<a href="{% url 'pages:home' %}" class="footer-logo" aria-label="Communication to Care home">
{% include "partials/_brand_icon.html" with placement="footer" %}
<span class="brand-name">Radiuma</span>
<span class="brand-name">Communication to Care</span>
</a>
<p class="footer-tagline">
Advancing medical imaging and radiomics research through innovative, standardized software solutions.
Turning complex medical-imaging evidence into clearer, reproducible research conversations.
</p>
{% include "partials/_contact_discord.html" with button_class="btn-ghost btn-sm" %}
</div>
@@ -22,7 +22,7 @@
<h3 class="footer-heading">Navigation</h3>
<ul class="footer-links" role="list">
<li><a href="{% url 'pages:home' %}">Home</a></li>
<li><a href="{% url 'pages:about' %}">What is Radiuma</a></li>
<li><a href="{% url 'pages:about' %}">What is Communication to Care</a></li>
<li><a href="{% url 'products:overview' %}">Products</a></li>
<li><a href="{% url 'pages:faq' %}">FAQ</a></li>
<li><a href="{% url 'pages:contact' %}">Contact</a></li>
@@ -37,7 +37,7 @@
<li><a href="{{ product.get_absolute_url }}">{{ product.name }}</a></li>
{% endfor %}
{% if footer_main_products_has_more %}
<li><a href="{% url 'products:main_product_detail' main_slug='radiuma' %}">more</a></li>
<li><a href="{% url 'products:overview' %}">More products</a></li>
{% endif %}
</ul>
</div>
@@ -55,12 +55,10 @@
<div class="footer-bottom">
<p class="footer-copy">
&copy; {% now "Y" %} Radiuma. All rights reserved.
&copy; {% now "Y" %} Communication to Care. All rights reserved.
</p>
<p class="footer-credit">
Developed at <a href="https://www.qurit.ca" target="_blank" rel="noopener noreferrer">Qurit Lab</a>,
<a href="https://www.ubc.ca" target="_blank" rel="noopener noreferrer">University of British Columbia</a>
&amp; <a href="https://www.bccrc.ca" target="_blank" rel="noopener noreferrer">BC Cancer Research Institute</a>
Research collaboration at <a href="https://com2care.com">com2care.com</a>
</p>
</div>
+2 -2
View File
@@ -2,9 +2,9 @@
<nav class="navbar" id="navbar" role="navigation" aria-label="Main navigation">
<div class="navbar-container">
<a href="{% url 'pages:home' %}" class="navbar-brand" aria-label="Radiuma home">
<a href="{% url 'pages:home' %}" class="navbar-brand" aria-label="Communication to Care home">
{% include "partials/_brand_icon.html" with placement="navbar" %}
<span class="brand-name">Radiuma</span>
<span class="brand-name">com2care</span>
</a>
<button class="navbar-toggle" id="navbarToggle" aria-expanded="false" aria-controls="navbarMenu" aria-label="Toggle navigation">
+1 -1
View File
@@ -39,7 +39,7 @@
{% if main_product.image %}
<img src="{{ main_product.image.url }}" alt="{{ main_product.name }}" loading="lazy" />
{% else %}
<img src="{% static 'images/logo.png' %}" alt="{{ main_product.name }} logo" loading="lazy" class="product-logo-img" />
<img src="{% static 'images/com2care-logo.svg' %}" alt="{{ main_product.name }} logo" loading="lazy" class="product-logo-img" />
{% endif %}
</div>
<div class="product-detail-text">
+1 -1
View File
@@ -2,7 +2,7 @@
{% load static %}
{% block title %}Products{% endblock %}
{% block meta_description %}Explore Radiuma's suite of medical imaging and radiomics software products.{% endblock %}
{% block meta_description %}Explore com2care tools for collaborative medical imaging, radiomics, reproducible workflows, and research communication.{% endblock %}
{% block content %}
+1 -1
View File
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block title %}{{ library_title }}{% endblock %}
{% block meta_description %}Browse {{ library_title|lower }} from Radiuma.{% endblock %}
{% block meta_description %}Browse {{ library_title|lower }} selected for the Communication to Care learning library.{% endblock %}
{% block content %}
<section class="page-hero video-library-hero" aria-labelledby="video-library-title">
@@ -1 +0,0 @@
a
@@ -1 +0,0 @@
binary-payload
@@ -1 +0,0 @@
x
@@ -1 +0,0 @@
gz