diff --git a/backend/contributions/views.py b/backend/contributions/views.py index f8573bd0..1084db16 100644 --- a/backend/contributions/views.py +++ b/backend/contributions/views.py @@ -2066,9 +2066,15 @@ class FeaturedContentViewSet(viewsets.ReadOnlyModelViewSet): pagination_class = None def get_queryset(self): - queryset = FeaturedContent.objects.filter(status='active').select_related( + include_inactive = ( + self.request.query_params.get('include_inactive', '').lower() + in ('1', 'true', 'yes') + ) + queryset = FeaturedContent.objects.select_related( 'user', 'contribution', 'contribution__contribution_type' ).order_by('order', '-created_at') + if not include_inactive: + queryset = queryset.filter(status='active') content_type = self.request.query_params.get('type') if content_type: queryset = queryset.filter(content_type=content_type) diff --git a/backend/gen_tv/admin.py b/backend/gen_tv/admin.py index 3a330bfe..0258df6e 100644 --- a/backend/gen_tv/admin.py +++ b/backend/gen_tv/admin.py @@ -1,10 +1,19 @@ from django.contrib import admin +from utils.admin_mixins import CloudinaryUploadMixin + from .models import Stream @admin.register(Stream) -class StreamAdmin(admin.ModelAdmin): +class StreamAdmin(CloudinaryUploadMixin, admin.ModelAdmin): + cloudinary_upload_fields = { + 'image_url': { + 'public_id_field': 'image_public_id', + 'folder': 'tally/gen-tv', + }, + } + list_display = ( 'title', 'category', @@ -18,7 +27,7 @@ class StreamAdmin(admin.ModelAdmin): search_fields = ('title', 'description') prepopulated_fields = {'slug': ('title',)} date_hierarchy = 'starts_at' - readonly_fields = ('created_at', 'updated_at') + readonly_fields = ('created_at', 'updated_at', 'image_public_id') fieldsets = ( (None, { 'fields': ('title', 'slug', 'description', 'is_active'), @@ -28,6 +37,7 @@ class StreamAdmin(admin.ModelAdmin): }), ('Media', { 'fields': ('url', 'image_url'), + 'description': 'Upload a cover image directly or paste a Cloudinary URL.', }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), diff --git a/backend/gen_tv/migrations/0004_stream_image_public_id_alter_stream_image_url.py b/backend/gen_tv/migrations/0004_stream_image_public_id_alter_stream_image_url.py new file mode 100644 index 00000000..6ee0828e --- /dev/null +++ b/backend/gen_tv/migrations/0004_stream_image_public_id_alter_stream_image_url.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.5 on 2026-05-15 11:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gen_tv', '0003_upsert_fud_markets_stream'), + ] + + operations = [ + migrations.AddField( + model_name='stream', + name='image_public_id', + field=models.CharField(blank=True, help_text='Cloudinary public ID for thumbnail / cover image.', max_length=255), + ), + migrations.AlterField( + model_name='stream', + name='image_url', + field=models.URLField(blank=True, help_text='Cloudinary URL for thumbnail / cover image.', max_length=500), + ), + ] diff --git a/backend/gen_tv/models.py b/backend/gen_tv/models.py index f7c18812..124d104c 100644 --- a/backend/gen_tv/models.py +++ b/backend/gen_tv/models.py @@ -26,7 +26,12 @@ class Category(models.TextChoices): image_url = models.URLField( max_length=500, blank=True, - help_text="Thumbnail / cover image URL.", + help_text="Cloudinary URL for thumbnail / cover image.", + ) + image_public_id = models.CharField( + max_length=255, + blank=True, + help_text="Cloudinary public ID for thumbnail / cover image.", ) starts_at = models.DateTimeField( help_text="Scheduled start time (used for sorting and status).", diff --git a/backend/partners/admin.py b/backend/partners/admin.py index 66cec71b..6e2b16e4 100644 --- a/backend/partners/admin.py +++ b/backend/partners/admin.py @@ -1,10 +1,19 @@ from django.contrib import admin +from utils.admin_mixins import CloudinaryUploadMixin + from .models import Partner @admin.register(Partner) -class PartnerAdmin(admin.ModelAdmin): +class PartnerAdmin(CloudinaryUploadMixin, admin.ModelAdmin): + cloudinary_upload_fields = { + 'logo_url': { + 'public_id_field': 'logo_public_id', + 'folder': 'tally/partners', + }, + } + list_display = ( 'name', 'display_order', @@ -16,13 +25,14 @@ class PartnerAdmin(admin.ModelAdmin): list_filter = ('is_active',) search_fields = ('name', 'description') prepopulated_fields = {'slug': ('name',)} - readonly_fields = ('created_at', 'updated_at') + readonly_fields = ('created_at', 'updated_at', 'logo_public_id') fieldsets = ( (None, { 'fields': ('name', 'slug', 'description', 'is_active'), }), ('URLs', { 'fields': ('logo_url', 'website_url', 'url'), + 'description': 'Upload a logo directly or paste a Cloudinary URL.', }), ('Display', { 'fields': ('display_order',), diff --git a/backend/partners/migrations/0002_partner_logo_public_id_alter_partner_logo_url.py b/backend/partners/migrations/0002_partner_logo_public_id_alter_partner_logo_url.py new file mode 100644 index 00000000..c5937be2 --- /dev/null +++ b/backend/partners/migrations/0002_partner_logo_public_id_alter_partner_logo_url.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.5 on 2026-05-15 11:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('partners', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='partner', + name='logo_public_id', + field=models.CharField(blank=True, help_text='Cloudinary public ID for partner logo.', max_length=255), + ), + migrations.AlterField( + model_name='partner', + name='logo_url', + field=models.URLField(blank=True, help_text='Cloudinary URL for partner logo.', max_length=500), + ), + ] diff --git a/backend/partners/models.py b/backend/partners/models.py index f828f67d..a95c40c7 100644 --- a/backend/partners/models.py +++ b/backend/partners/models.py @@ -9,7 +9,8 @@ class Partner(BaseModel): name = models.CharField(max_length=200) slug = models.SlugField(max_length=200, unique=True) description = models.TextField(blank=True) - logo_url = models.URLField(max_length=500, blank=True) + logo_url = models.URLField(max_length=500, blank=True, help_text="Cloudinary URL for partner logo.") + logo_public_id = models.CharField(max_length=255, blank=True, help_text="Cloudinary public ID for partner logo.") website_url = models.URLField( max_length=500, help_text="Official website (primary redirect target).", diff --git a/frontend/src/routes/GenNews.svelte b/frontend/src/routes/GenNews.svelte index d4309074..f6c7d399 100644 --- a/frontend/src/routes/GenNews.svelte +++ b/frontend/src/routes/GenNews.svelte @@ -134,7 +134,7 @@ onMount(async () => { try { - const heroRes = await featuredAPI.getHero(); + const heroRes = await featuredAPI.getFeatured({ type: 'hero', include_inactive: true }); const fetchedAnnouncements = dedupeById( asArray(heroRes.data).map((item) => normalizeAnnouncement(item, 'hero')) ); diff --git a/package-lock.json b/package-lock.json index dda598e8..d4fb4052 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "irvine", + "name": "andorra", "lockfileVersion": 3, "requires": true, "packages": {