feat: dynamic citation text and logo

This commit is contained in:
mohamad
2026-05-20 16:02:16 +03:30
parent 2341b32d4c
commit 24dd24e1c9
11 changed files with 181 additions and 29 deletions
+32 -1
View File
@@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from apps.pages.models import DownloadItem, FAQEntry, HomepageSection, HomepageSectionItem
from apps.pages.models import DownloadItem, FAQEntry, HeroSection, HomepageSection, HomepageSectionItem
from apps.products.models import Article, ArticleSection, MainProduct, SubProduct
MAIN_PRODUCTS = [
@@ -395,11 +395,13 @@ class Command(BaseCommand):
DownloadItem.objects.all().delete()
HomepageSectionItem.objects.all().delete()
HomepageSection.objects.all().delete()
HeroSection.objects.all().delete()
self._seed_products()
self._seed_faq()
self._seed_downloads()
self._seed_homepage_sections()
self._seed_hero()
self.stdout.write(self.style.SUCCESS("Content seeded successfully."))
@@ -509,3 +511,32 @@ class Command(BaseCommand):
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",
"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."
),
"primary_cta_text": "Get Radiuma",
"primary_cta_url": "/products/",
"secondary_cta_text": "About Radiuma",
"secondary_cta_url": "/about/",
"image_alt": "Radiuma application — main workflow view",
}
hero = HeroSection.objects.first()
if hero is None:
HeroSection.objects.create(**data)
self.stdout.write(" Created hero section")
else:
for field, value in data.items():
if field == "image":
continue
setattr(hero, field, value)
hero.save()
self.stdout.write(" Updated hero section")
+29
View File
@@ -1,4 +1,6 @@
from django.contrib import admin
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import (
AboutSection,
@@ -6,11 +8,38 @@ from .models import (
ContactSubmission,
DownloadItem,
FAQEntry,
HeroSection,
HomepageSection,
HomepageSectionItem,
)
@admin.register(HeroSection)
class HeroSectionAdmin(admin.ModelAdmin):
fieldsets = (
("Badge", {"fields": ("badge",)}),
("Title", {"fields": ("title", "title_highlight")}),
("Text", {"fields": ("subtitle", "description")}),
("Primary Button", {"fields": ("primary_cta_text", "primary_cta_url")}),
("Secondary Button", {"fields": ("secondary_cta_text", "secondary_cta_url")}),
("Image", {"fields": ("image", "image_alt")}),
)
def has_add_permission(self, request):
return not HeroSection.objects.exists()
def has_delete_permission(self, request, obj=None):
return False
def changelist_view(self, request, extra_context=None):
hero = HeroSection.objects.first()
if hero:
return HttpResponseRedirect(
reverse("admin:pages_herosection_change", args=[hero.pk])
)
return super().changelist_view(request, extra_context)
class HomepageSectionItemInline(admin.TabularInline):
model = HomepageSectionItem
extra = 1
@@ -0,0 +1,34 @@
# Generated by Django 5.0.2 on 2026-05-20 09:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pages', '0006_homepagesectionitem_image_supporters'),
]
operations = [
migrations.CreateModel(
name='HeroSection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('badge', models.CharField(blank=True, help_text="Small label above the title (e.g. 'Developing since 2021').", max_length=200)),
('title', models.CharField(blank=True, help_text="Main title line (e.g. 'Radiuma,').", max_length=300)),
('title_highlight', models.CharField(blank=True, help_text="Second title line shown in gradient colour (e.g. 'A Powerful Workflow Generator').", max_length=300)),
('subtitle', models.CharField(blank=True, help_text="Short line below the title (e.g. 'for Standardized Radiomics Analysis…').", max_length=500)),
('description', models.TextField(blank=True, help_text='Longer paragraph below the subtitle.')),
('primary_cta_text', models.CharField(blank=True, help_text='Primary button label.', max_length=100)),
('primary_cta_url', models.CharField(blank=True, help_text='Primary button URL (relative or absolute).', max_length=300)),
('secondary_cta_text', models.CharField(blank=True, help_text='Secondary (ghost) button label.', max_length=100)),
('secondary_cta_url', models.CharField(blank=True, help_text='Secondary (ghost) button URL.', max_length=300)),
('image', models.ImageField(blank=True, help_text='App preview screenshot shown on the right.', null=True, upload_to='hero/')),
('image_alt', models.CharField(blank=True, help_text='Alt text for the preview image.', max_length=300)),
],
options={
'verbose_name': 'Hero Section',
'verbose_name_plural': 'Hero Section',
},
),
]
+21
View File
@@ -3,6 +3,27 @@ from django.db import models
from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_MARKDOWN, FORMAT_PLAIN, render_content
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_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.")
primary_cta_text = models.CharField(max_length=100, blank=True, help_text="Primary button label.")
primary_cta_url = models.CharField(max_length=300, blank=True, help_text="Primary button URL (relative or absolute).")
secondary_cta_text = models.CharField(max_length=100, blank=True, help_text="Secondary (ghost) button label.")
secondary_cta_url = models.CharField(max_length=300, blank=True, help_text="Secondary (ghost) button URL.")
image = models.ImageField(upload_to="hero/", blank=True, null=True, help_text="App preview screenshot shown on the right.")
image_alt = models.CharField(max_length=300, blank=True, help_text="Alt text for the preview image.")
class Meta:
verbose_name = "Hero Section"
verbose_name_plural = "Hero Section"
def __str__(self):
return "Hero Section"
class HomepageSection(models.Model):
TYPE_FEATURES = "features"
TYPE_SCREENSHOTS = "screenshots"
+2 -1
View File
@@ -8,7 +8,7 @@ from django.views.generic import ListView, TemplateView
from apps.products.models import SubProduct
from .forms import ContactForm
from .models import AboutSection, ContactSubmission, FAQEntry, HomepageSection
from .models import AboutSection, ContactSubmission, FAQEntry, HeroSection, HomepageSection
class HomeView(TemplateView):
@@ -16,6 +16,7 @@ class HomeView(TemplateView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["hero"] = HeroSection.objects.first()
ctx["homepage_sections"] = (
HomepageSection.objects.filter(is_active=True)
.prefetch_related("items")
+1 -1
View File
@@ -106,7 +106,7 @@ class ArticleAdmin(admin.ModelAdmin):
fieldsets = (
(None, {"fields": ("sub_product", "badge", "title")}),
("Description", {"fields": ("description_format", "description")}),
("Citation Badge", {"fields": ("citation_count_display",), "description": "Set a number to show the dashed citation circle badge at the bottom-right of the article card. Leave blank to hide it."}),
("Citation Badge", {"fields": ("citation_count_display", "citation_count_label"), "description": "Number and optional label for the dashed citation circle badge. Both are optional — a label without a number shows an empty circle with the label; neither hides the badge entirely."}),
("Settings", {"fields": ("order",)}),
)
@@ -0,0 +1,23 @@
# Generated by Django 5.0.2 on 2026-05-20 12:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0007_article_citation_count_display'),
]
operations = [
migrations.AddField(
model_name='article',
name='citation_count_label',
field=models.CharField(blank=True, default='', help_text="Optional label shown below the number in the citation circle badge (e.g. 'cited'). Leave blank to show no label.", max_length=50),
),
migrations.AlterField(
model_name='article',
name='citation_count_display',
field=models.PositiveSmallIntegerField(blank=True, help_text='Optional number shown in the dashed citation circle badge. Leave blank to show an empty circle (if a label is set) or hide the badge entirely.', null=True),
),
]
+7 -1
View File
@@ -139,7 +139,13 @@ class Article(models.Model):
citation_count_display = models.PositiveSmallIntegerField(
null=True,
blank=True,
help_text="Optional number shown in the dashed citation circle badge at the bottom of the article card. Leave blank to hide the badge.",
help_text="Optional number shown in the dashed citation circle badge. Leave blank to show an empty circle (if a label is set) or hide the badge entirely.",
)
citation_count_label = models.CharField(
max_length=50,
blank=True,
default="",
help_text="Optional label shown below the number in the citation circle badge (e.g. 'cited'). Leave blank to show no label.",
)
order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
+1
View File
@@ -345,6 +345,7 @@ h1, h2, h3, h4, h5, h6 {
width: 26px;
height: 26px;
border-radius: 8px;
border: 1px solid #c4b5fd;
object-fit: cover;
display: block;
flex-shrink: 0;
+20 -14
View File
@@ -16,29 +16,35 @@
</div>
<div class="container hero-content">
<div class="hero-text">
<div class="hero-badge">Developing since 2021</div>
{% if hero %}
{% if hero.badge %}<div class="hero-badge">{{ hero.badge }}</div>{% endif %}
{% if hero.title or hero.title_highlight %}
<h1 class="hero-title" id="hero-heading">
Radiuma,
<br />
<span class="gradient-text">A Powerful Workflow Generator</span>
{% if hero.title %}{{ hero.title }}{% endif %}
{% if hero.title and hero.title_highlight %}<br />{% endif %}
{% if hero.title_highlight %}<span class="gradient-text">{{ hero.title_highlight }}</span>{% endif %}
</h1>
<p class="hero-subtitle">
for Standardized Radiomics Analysis and Medical Image Visualization
</p>
<p class="hero-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.
</p>
{% endif %}
{% if hero.subtitle %}<p class="hero-subtitle">{{ hero.subtitle }}</p>{% endif %}
{% if hero.description %}<p class="hero-description">{{ hero.description }}</p>{% endif %}
{% if hero.primary_cta_text or hero.secondary_cta_text %}
<div class="hero-actions">
<a href="{% url 'products:overview' %}" class="btn-primary">Get Radiuma</a>
<a href="{% url 'pages:about' %}" class="btn-ghost">About Radiuma</a>
{% if hero.primary_cta_text %}<a href="{{ hero.primary_cta_url }}" class="btn-primary">{{ hero.primary_cta_text }}</a>{% endif %}
{% if hero.secondary_cta_text %}<a href="{{ hero.secondary_cta_url }}" class="btn-ghost">{{ hero.secondary_cta_text }}</a>{% endif %}
</div>
{% endif %}
{% endif %}
</div>
{% if 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 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" />
{% endif %}
</div>
{% endif %}
</div>
</section>
+7 -7
View File
@@ -188,16 +188,16 @@
</ol>
</div>
{% endif %}
{% if article.citation_count_display is not None %}
{% if article.citation_count_display is not None or article.citation_count_label %}
{% if article.citations.exists %}
<a href="#article-{{ article.pk }}-citations" class="citation-count-badge" aria-label="{{ article.citation_count_display }} citation{{ article.citation_count_display|pluralize }}">
<span class="citation-count-number">{{ article.citation_count_display }}</span>
<span class="citation-count-label">cited</span>
<a href="#article-{{ article.pk }}-citations" class="citation-count-badge" aria-label="{% if article.citation_count_display %}{{ article.citation_count_display }} {% endif %}{% if article.citation_count_label %}{{ article.citation_count_label }}{% endif %}">
{% if article.citation_count_display is not None %}<span class="citation-count-number">{{ article.citation_count_display }}</span>{% endif %}
{% if article.citation_count_label %}<span class="citation-count-label">{{ article.citation_count_label }}</span>{% endif %}
</a>
{% else %}
<div class="citation-count-badge" aria-label="{{ article.citation_count_display }} citation{{ article.citation_count_display|pluralize }}">
<span class="citation-count-number">{{ article.citation_count_display }}</span>
<span class="citation-count-label">cited</span>
<div class="citation-count-badge" aria-label="{% if article.citation_count_display %}{{ article.citation_count_display }} {% endif %}{% if article.citation_count_label %}{{ article.citation_count_label }}{% endif %}">
{% if article.citation_count_display is not None %}<span class="citation-count-number">{{ article.citation_count_display }}</span>{% endif %}
{% if article.citation_count_label %}<span class="citation-count-label">{{ article.citation_count_label }}</span>{% endif %}
</div>
{% endif %}
{% endif %}