feat: pagination for videos and icon separating
This commit is contained in:
+20
-3
@@ -8,19 +8,36 @@ from .models import SiteBranding, SiteContact
|
||||
@admin.register(SiteBranding)
|
||||
class SiteBrandingAdmin(admin.ModelAdmin):
|
||||
fieldsets = (
|
||||
("Icon", {"fields": ("icon", "icon_alt")}),
|
||||
(
|
||||
"Brand assets",
|
||||
{
|
||||
"fields": (
|
||||
"icon",
|
||||
"icon_alt",
|
||||
"website_icon",
|
||||
"hero_logo",
|
||||
"hero_logo_alt",
|
||||
),
|
||||
"description": (
|
||||
"Manage each placement independently. Removing an upload restores "
|
||||
"the existing Radiuma asset for that placement."
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Sizes",
|
||||
{
|
||||
"fields": ("navbar_icon_size", "footer_icon_size"),
|
||||
"description": "Square dimensions in pixels for each placement.",
|
||||
"description": (
|
||||
"Set the logo height for each placement. Its width is calculated "
|
||||
"automatically so the uploaded image is never cropped or stretched."
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Appearance",
|
||||
{
|
||||
"fields": (
|
||||
"object_fit",
|
||||
"show_border",
|
||||
"border_width",
|
||||
"border_color",
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 5.2.13 on 2026-08-01 14:10
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_site_contact'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sitebranding',
|
||||
name='hero_logo',
|
||||
field=models.ImageField(blank=True, help_text='Large brand artwork shown in the homepage hero. Leave empty to use the existing hero image.', null=True, upload_to='branding/hero/'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sitebranding',
|
||||
name='hero_logo_alt',
|
||||
field=models.CharField(blank=True, default='Radiuma', help_text='Accessible description for the homepage hero logo.', max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
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 Radiuma icons.', null=True, upload_to='branding/icons/', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=('ico', 'png', 'svg', 'jpg', 'jpeg', 'webp'))]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sitebranding',
|
||||
name='footer_icon_size',
|
||||
field=models.PositiveSmallIntegerField(default=34, help_text='Maximum height in pixels for the footer logo. Width scales automatically.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sitebranding',
|
||||
name='icon',
|
||||
field=models.ImageField(blank=True, help_text='Brand logo shown in the site navigation and footer. Leave empty to use the default logo.', null=True, upload_to='branding/', verbose_name='Header and footer logo'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sitebranding',
|
||||
name='navbar_icon_size',
|
||||
field=models.PositiveSmallIntegerField(default=26, help_text='Maximum height in pixels for the header logo. Width scales automatically.'),
|
||||
),
|
||||
]
|
||||
+36
-5
@@ -1,3 +1,4 @@
|
||||
from django.core.validators import FileExtensionValidator
|
||||
from django.db import models
|
||||
|
||||
|
||||
@@ -13,20 +14,50 @@ class SiteBranding(models.Model):
|
||||
upload_to="branding/",
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Logo shown in the site header and footer. Leave empty to use the default static icon.",
|
||||
verbose_name="Header and footer logo",
|
||||
help_text="Brand logo shown in the site navigation and footer. Leave empty to use the default logo.",
|
||||
)
|
||||
icon_alt = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
help_text="Alt text for the brand icon (decorative icons can stay empty).",
|
||||
)
|
||||
website_icon = models.FileField(
|
||||
upload_to="branding/icons/",
|
||||
blank=True,
|
||||
null=True,
|
||||
validators=[
|
||||
FileExtensionValidator(
|
||||
allowed_extensions=("ico", "png", "svg", "jpg", "jpeg", "webp")
|
||||
)
|
||||
],
|
||||
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."
|
||||
),
|
||||
)
|
||||
hero_logo = models.ImageField(
|
||||
upload_to="branding/hero/",
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=(
|
||||
"Large brand artwork shown in the homepage hero. Leave empty to use "
|
||||
"the existing hero image."
|
||||
),
|
||||
)
|
||||
hero_logo_alt = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
default="Radiuma",
|
||||
help_text="Accessible description for the homepage hero logo.",
|
||||
)
|
||||
navbar_icon_size = models.PositiveSmallIntegerField(
|
||||
default=26,
|
||||
help_text="Width and height in pixels for the header icon.",
|
||||
help_text="Maximum height in pixels for the header logo. Width scales automatically.",
|
||||
)
|
||||
footer_icon_size = models.PositiveSmallIntegerField(
|
||||
default=34,
|
||||
help_text="Width and height in pixels for the footer icon.",
|
||||
help_text="Maximum height in pixels for the footer logo. Width scales automatically.",
|
||||
)
|
||||
show_border = models.BooleanField(
|
||||
default=False,
|
||||
@@ -61,9 +92,9 @@ class SiteBranding(models.Model):
|
||||
|
||||
def icon_style(self, size_px):
|
||||
parts = [
|
||||
f"width:{size_px}px",
|
||||
"width:auto",
|
||||
f"height:{size_px}px",
|
||||
f"object-fit:{self.object_fit}",
|
||||
"object-fit:contain",
|
||||
]
|
||||
if self.show_border:
|
||||
parts.append(f"border:{self.border_width}px solid {self.border_color}")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from apps.core.context_processors import site_branding
|
||||
from apps.core.models import SiteBranding
|
||||
@@ -17,7 +18,9 @@ class SiteBrandingModelTests(TestCase):
|
||||
branding.border_width = 2
|
||||
branding.navbar_icon_size = 30
|
||||
style = branding.navbar_icon_style
|
||||
self.assertIn("width:30px", style)
|
||||
self.assertIn("width:auto", style)
|
||||
self.assertIn("height:30px", style)
|
||||
self.assertIn("object-fit:contain", style)
|
||||
self.assertIn("border:2px solid #ffffff", style)
|
||||
|
||||
def test_icon_style_omits_border_when_disabled(self):
|
||||
@@ -33,3 +36,26 @@ class SiteBrandingContextProcessorTests(TestCase):
|
||||
ctx = site_branding(request)
|
||||
self.assertIn("site_branding", ctx)
|
||||
self.assertIsInstance(ctx["site_branding"], SiteBranding)
|
||||
|
||||
|
||||
class SiteBrandingTemplateTests(TestCase):
|
||||
def test_custom_brand_assets_are_rendered_independently(self):
|
||||
branding = SiteBranding.load()
|
||||
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.save()
|
||||
|
||||
response = self.client.get(reverse("pages:home"))
|
||||
|
||||
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"')
|
||||
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user