From d299f7ffd338a65d9470f7837ca79168b52f0be4 Mon Sep 17 00:00:00 2001 From: Ross Date: Thu, 4 Dec 2025 22:16:33 +0000 Subject: [PATCH] Add BackgroundImage model and enhance card templates with dynamic backgrounds --- .gitignore | 58 +++++++++++++++++++++++ cards/admin.py | 9 +++- cards/migrations/0002_backgroundimage.py | 26 ++++++++++ cards/models.py | 26 ++++++++++ cards/templates/cards/_card_item.html | 8 +++- cards/templates/cards/_cards_list.html | 2 +- cards/templates/cards/base.html | 56 +++++++++++++++++++++- cards/views.py | 18 ++++--- db.sqlite3 | Bin 135168 -> 139264 bytes templates/cards/_card_item.html | 8 +++- templates/cards/_cards_list.html | 2 +- templates/cards/base.html | 56 +++++++++++++++++++++- 12 files changed, 253 insertions(+), 16 deletions(-) create mode 100644 cards/migrations/0002_backgroundimage.py diff --git a/.gitignore b/.gitignore index e69de29..b9cbfd5 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,58 @@ +# OS +.DS_Store +Thumbs.db +desktop.ini +$RECYCLE.BIN/ + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log + +# Node +node_modules/ +dist/ +build/ +out/ +coverage/ +.nyc_output/ + +# Environment +.env +.env.*.local + +# IDEs and editors +.vscode/ +.idea/ +*.sublime-workspace +*.sublime-project +*.code-workspace + +# Package managers +package-lock.json +.pnpm-store/ +# (keep yarn.lock if you rely on it; remove above line to keep package-lock.json in repo) + +# Python +__pycache__/ +*.py[cod] +venv/ +ENV/ +env/ + +# Java / JVM +target/ +*.class +*.jar +*.war +*.ear +*.iml + +# Misc +*.sqlite +*.db +*.swp +*~ \ No newline at end of file diff --git a/cards/admin.py b/cards/admin.py index cde885f..e804f4c 100644 --- a/cards/admin.py +++ b/cards/admin.py @@ -1,8 +1,15 @@ from django.contrib import admin -from .models import Dinosaur +from .models import Dinosaur, BackgroundImage @admin.register(Dinosaur) class DinosaurAdmin(admin.ModelAdmin): list_display = ('name', 'era', 'diet', 'speed', 'weight', 'intelligence') search_fields = ('name', 'era', 'diet') + + +@admin.register(BackgroundImage) +class BackgroundImageAdmin(admin.ModelAdmin): + list_display = ('__str__', 'is_active', 'uploaded_at') + list_filter = ('is_active',) + search_fields = ('title',) diff --git a/cards/migrations/0002_backgroundimage.py b/cards/migrations/0002_backgroundimage.py new file mode 100644 index 0000000..154f8c5 --- /dev/null +++ b/cards/migrations/0002_backgroundimage.py @@ -0,0 +1,26 @@ +# Generated by Django 6.0 on 2025-12-04 22:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cards', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='BackgroundImage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(blank=True, max_length=200)), + ('image', models.ImageField(upload_to='backgrounds/')), + ('is_active', models.BooleanField(default=False, help_text='Mark as the active background')), + ('uploaded_at', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'ordering': ['-is_active', '-uploaded_at'], + }, + ), + ] diff --git a/cards/models.py b/cards/models.py index 23ed797..f63bc37 100644 --- a/cards/models.py +++ b/cards/models.py @@ -33,3 +33,29 @@ class Dinosaur(models.Model): def __str__(self): return self.name + + +class BackgroundImage(models.Model): + """Background images used behind card renders. + + Admins can upload multiple backgrounds and mark one as active. Templates + will use BackgroundImage.get_active() to obtain the active background. + """ + title = models.CharField(max_length=200, blank=True) + image = models.ImageField(upload_to='backgrounds/') + is_active = models.BooleanField(default=False, help_text='Mark as the active background') + uploaded_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['-is_active', '-uploaded_at'] + + def __str__(self): + return self.title or f"Background #{self.pk}" + + @classmethod + def get_active(cls): + """Return the active BackgroundImage if set, otherwise the most recent one or None.""" + active = cls.objects.filter(is_active=True).order_by('-uploaded_at').first() + if active: + return active + return cls.objects.order_by('-uploaded_at').first() diff --git a/cards/templates/cards/_card_item.html b/cards/templates/cards/_card_item.html index 270086a..2889389 100644 --- a/cards/templates/cards/_card_item.html +++ b/cards/templates/cards/_card_item.html @@ -1,4 +1,5 @@ -
+

{{ card.name }}

@@ -20,4 +21,9 @@
+ {% if card.image %} +
+ {{ card.name }} +
+ {% endif %}
diff --git a/cards/templates/cards/_cards_list.html b/cards/templates/cards/_cards_list.html index a6252c0..b2d249a 100644 --- a/cards/templates/cards/_cards_list.html +++ b/cards/templates/cards/_cards_list.html @@ -2,7 +2,7 @@
{% for card in cards %}
- {% include 'cards/_card_item.html' with card=card %} + {% include 'cards/_card_item.html' with card=card background=background %}
{% empty %}
No dinosaurs yet — add one!
diff --git a/cards/templates/cards/base.html b/cards/templates/cards/base.html index dc83a45..5b1586f 100644 --- a/cards/templates/cards/base.html +++ b/cards/templates/cards/base.html @@ -7,19 +7,71 @@
-

TopTrumps — Dinosaurs

+
+
+

TopTrumps — Dinosaurs

+
+
+
+ +
+
+
{% block content %}{% endblock %}
+ diff --git a/cards/views.py b/cards/views.py index bb76ab8..f0664ad 100644 --- a/cards/views.py +++ b/cards/views.py @@ -1,7 +1,7 @@ from django.shortcuts import render, get_object_or_404, redirect from django.http import HttpResponse, HttpResponseBadRequest from django.template.loader import render_to_string -from .models import Dinosaur +from .models import Dinosaur, BackgroundImage from .forms import DinosaurForm @@ -11,18 +11,21 @@ def is_htmx(request): def card_list(request): cards = Dinosaur.objects.all() + background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None if is_htmx(request): - return render(request, 'cards/_cards_list.html', {'cards': cards}) - return render(request, 'cards/list.html', {'cards': cards}) + return render(request, 'cards/_cards_list.html', {'cards': cards, 'background': background}) + return render(request, 'cards/list.html', {'cards': cards, 'background': background}) def card_create(request): + background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None + if request.method == 'POST': form = DinosaurForm(request.POST, request.FILES) if form.is_valid(): card = form.save() if is_htmx(request): - card_html = render_to_string('cards/_card_item.html', {'card': card}, request=request) + card_html = render_to_string('cards/_card_item.html', {'card': card, 'background': background}, request=request) # Also return an out-of-band fragment to replace/clear the form container form_html = render_to_string('cards/_form.html', {'form': DinosaurForm()}, request=request) oob = f'
{form_html}
' @@ -37,17 +40,18 @@ def card_create(request): else: form = DinosaurForm() - return render(request, 'cards/_form.html', {'form': form}) + return render(request, 'cards/_form.html', {'form': form, 'background': background}) def card_edit(request, pk): card = get_object_or_404(Dinosaur, pk=pk) + background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None if request.method == 'POST': form = DinosaurForm(request.POST, request.FILES, instance=card) if form.is_valid(): card = form.save() if is_htmx(request): - card_html = render_to_string('cards/_card_item.html', {'card': card}, request=request) + card_html = render_to_string('cards/_card_item.html', {'card': card, 'background': background}, request=request) # Clear the form container via OOB so the edit form disappears after successful save oob = '
' return HttpResponse(card_html + oob) @@ -60,7 +64,7 @@ def card_edit(request, pk): else: form = DinosaurForm(instance=card) - return render(request, 'cards/_form.html', {'form': form, 'card': card}) + return render(request, 'cards/_form.html', {'form': form, 'card': card, 'background': background}) def card_delete(request, pk): diff --git a/db.sqlite3 b/db.sqlite3 index 7bda041cc1aeb75cc1d56eade30f6e3f7f28674d..eeddcfd596a5b5681842b35d399aa352bb0167d8 100644 GIT binary patch delta 894 zcmZozz|nAkV}i7xCIbV55)i|H%|snzM$L^0OZb^ZxSmXA7f@&7da~J2U<;R(g$OHy zxT#}uVo^%5fq{Whd{Sa^c6w2MXk%5t^uAz~xfr)~Vk(Hr|m8prIp^>4X zvEgPp=^PP8g~{Fe9*i85ujp$Fi*NxgKml2mNkATpR5nHd>56u_iBn3MyPvYTV?FK=KJn9iEOsK~*)44mrXCr&in9-qM2#l^(5 zfBR`i#@$S@P3*?(;^yXz&AKItNja(D1c4r8SOt}wgIpa$TopnboqSvsl(4Fq?8sxv zT9R3klR7=eiBW2LZz7}U^f&2@T$3|6rR7Tta`F>XQd8m+OOzB+5=&A`GILWE{QN@{ z{6c+vG`DZaV+;ut;oy&D;D5`1kN*_^9{&0KUHs+zz*sSG<(HD+U||r~#+HP)m!D_s JbXe3N000*46FL9@ delta 232 zcmZoTz|pXPV}i7x8Uq7^A`ruX^+X+GMzxIzOZb_Ex#cFa3#e~aRQSNPSx!1fgi&U4 zx4s7>`{XP7+Ty}oKrQV26%73E`0w+d=HJU-!M}jNd$V9d69421`yOT)?n9H=9V~&` zo47Y0@^)fjmf<}!nLPj`(!;y?Osqu_vkZUiWcCXn5n2AtvG4T=2Q%g-}*IsgD@?m~6| diff --git a/templates/cards/_card_item.html b/templates/cards/_card_item.html index 270086a..2889389 100644 --- a/templates/cards/_card_item.html +++ b/templates/cards/_card_item.html @@ -1,4 +1,5 @@ -
+

{{ card.name }}

@@ -20,4 +21,9 @@
+ {% if card.image %} +
+ {{ card.name }} +
+ {% endif %}
diff --git a/templates/cards/_cards_list.html b/templates/cards/_cards_list.html index a6252c0..b2d249a 100644 --- a/templates/cards/_cards_list.html +++ b/templates/cards/_cards_list.html @@ -2,7 +2,7 @@
{% for card in cards %}
- {% include 'cards/_card_item.html' with card=card %} + {% include 'cards/_card_item.html' with card=card background=background %}
{% empty %}
No dinosaurs yet — add one!
diff --git a/templates/cards/base.html b/templates/cards/base.html index dc83a45..5b1586f 100644 --- a/templates/cards/base.html +++ b/templates/cards/base.html @@ -7,19 +7,71 @@
-

TopTrumps — Dinosaurs

+
+
+

TopTrumps — Dinosaurs

+
+
+
+ +
+
+
{% block content %}{% endblock %}
+