diff --git a/apps/core/management/commands/seed_content.py b/apps/core/management/commands/seed_content.py index df3959c..6281454 100644 --- a/apps/core/management/commands/seed_content.py +++ b/apps/core/management/commands/seed_content.py @@ -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") diff --git a/apps/pages/admin.py b/apps/pages/admin.py index 8d798a0..e30ab78 100644 --- a/apps/pages/admin.py +++ b/apps/pages/admin.py @@ -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 diff --git a/apps/pages/migrations/0007_hero_section.py b/apps/pages/migrations/0007_hero_section.py new file mode 100644 index 0000000..9b7e321 --- /dev/null +++ b/apps/pages/migrations/0007_hero_section.py @@ -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', + }, + ), + ] diff --git a/apps/pages/models.py b/apps/pages/models.py index 83888ff..f87d85c 100644 --- a/apps/pages/models.py +++ b/apps/pages/models.py @@ -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" diff --git a/apps/pages/views.py b/apps/pages/views.py index 29d9480..62c43f9 100644 --- a/apps/pages/views.py +++ b/apps/pages/views.py @@ -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") diff --git a/apps/products/admin.py b/apps/products/admin.py index b1d8008..62486a7 100644 --- a/apps/products/admin.py +++ b/apps/products/admin.py @@ -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",)}), ) diff --git a/apps/products/migrations/0008_article_citation_count_label.py b/apps/products/migrations/0008_article_citation_count_label.py new file mode 100644 index 0000000..cbbf40f --- /dev/null +++ b/apps/products/migrations/0008_article_citation_count_label.py @@ -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), + ), + ] diff --git a/apps/products/models.py b/apps/products/models.py index 1223561..080a494 100644 --- a/apps/products/models.py +++ b/apps/products/models.py @@ -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) diff --git a/static/css/main.css b/static/css/main.css index a2942d0..3e06581 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -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; diff --git a/templates/pages/home.html b/templates/pages/home.html index 365875e..70d1000 100644 --- a/templates/pages/home.html +++ b/templates/pages/home.html @@ -16,29 +16,35 @@
-
Developing since 2021
-

- Radiuma, -
- A Powerful Workflow Generator -

-

- for Standardized Radiomics Analysis and Medical Image Visualization -

-

- 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. -

- + {% if hero %} + {% if hero.badge %}
{{ hero.badge }}
{% endif %} + {% if hero.title or hero.title_highlight %} +

+ {% if hero.title %}{{ hero.title }}{% endif %} + {% if hero.title and hero.title_highlight %}
{% endif %} + {% if hero.title_highlight %}{{ hero.title_highlight }}{% endif %} +

+ {% endif %} + {% if hero.subtitle %}

{{ hero.subtitle }}

{% endif %} + {% if hero.description %}

{{ hero.description }}

{% endif %} + {% if hero.primary_cta_text or hero.secondary_cta_text %} +
+ {% if hero.primary_cta_text %}{{ hero.primary_cta_text }}{% endif %} + {% if hero.secondary_cta_text %}{{ hero.secondary_cta_text }}{% endif %} +
+ {% endif %} + {% endif %}
+ {% if hero and hero.image or not hero %}
+ {% if hero and hero.image %} + {{ hero.image_alt }} + {% elif not hero %} Radiuma application — main workflow view + {% endif %}
+ {% endif %}
diff --git a/templates/products/sub_detail.html b/templates/products/sub_detail.html index 9445c7c..3b87c96 100644 --- a/templates/products/sub_detail.html +++ b/templates/products/sub_detail.html @@ -188,16 +188,16 @@ {% 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 %} - - {{ article.citation_count_display }} - cited + + {% if article.citation_count_display is not None %}{{ article.citation_count_display }}{% endif %} + {% if article.citation_count_label %}{{ article.citation_count_label }}{% endif %} {% else %} -
- {{ article.citation_count_display }} - cited +
+ {% if article.citation_count_display is not None %}{{ article.citation_count_display }}{% endif %} + {% if article.citation_count_label %}{{ article.citation_count_label }}{% endif %}
{% endif %} {% endif %}