This commit is contained in:
Ross
2025-12-06 18:31:01 +00:00
parent 3566666cc2
commit 0e81fe2235
8 changed files with 153 additions and 108 deletions
@@ -11,6 +11,9 @@
<div class="level-item"> <div class="level-item">
<a class="button is-light" href="{% url 'cards:list' %}">Back to cards</a> <a class="button is-light" href="{% url 'cards:list' %}">Back to cards</a>
</div> </div>
<div class="level-item">
<a class="button is-link" href="{% url 'cards:backgrounds_download_all' %}">Download all backgrounds</a>
</div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -2,7 +2,7 @@
{% block content %} {% block content %}
<section class="section"> <section class="section">
<h2 class="title is-4">Cards overview — bulk edit</h2> <h2 class="title is-4">Cards overview — bulk edit{% if card_count %} ({{ card_count }} card{{ card_count|pluralize }}){% endif %}</h2>
<p class="subtitle is-6">Edit multiple cards inline and save changes in one go.</p> <p class="subtitle is-6">Edit multiple cards inline and save changes in one go.</p>
<style> <style>
+5
View File
@@ -17,6 +17,11 @@
</div> </div>
</div> </div>
</div> </div>
{% if card_count is not None %}
<div class="level-item">
<span class="tag is-light">{{ card_count }} card{% if card_count > 1 %}s{% endif %}</span>
</div>
{% endif %}
<div id="htmx-form"></div> <div id="htmx-form"></div>
+1
View File
@@ -11,6 +11,7 @@ urlpatterns = [
path('preview/<int:pk>/', views.preview_card, name='preview'), path('preview/<int:pk>/', views.preview_card, name='preview'),
path('backgrounds/', views.background_list, name='backgrounds'), path('backgrounds/', views.background_list, name='backgrounds'),
path('backgrounds/create/', views.background_create, name='background_create'), path('backgrounds/create/', views.background_create, name='background_create'),
path('backgrounds/download-all/', views.backgrounds_download_all, name='backgrounds_download_all'),
path('backgrounds/<int:pk>/activate/', views.background_activate, name='background_activate'), path('backgrounds/<int:pk>/activate/', views.background_activate, name='background_activate'),
path('backgrounds/<int:pk>/delete/', views.background_delete, name='background_delete'), path('backgrounds/<int:pk>/delete/', views.background_delete, name='background_delete'),
path('overview/', views.cards_overview, name='overview'), path('overview/', views.cards_overview, name='overview'),
+38 -2
View File
@@ -1,6 +1,8 @@
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from django.template.loader import render_to_string from django.template.loader import render_to_string
import io
import zipfile
import os import os
from .models import Dinosaur, BackgroundImage from .models import Dinosaur, BackgroundImage
from .forms import DinosaurBulkFormSet from .forms import DinosaurBulkFormSet
@@ -62,6 +64,9 @@ def card_list(request):
'current_order': order, 'current_order': order,
} }
# Total (or filtered) card count for UI summary
context['card_count'] = qs.count()
if is_htmx(request): if is_htmx(request):
return render(request, 'cards/_cards_list.html', context) return render(request, 'cards/_cards_list.html', context)
return render(request, 'cards/list.html', context) return render(request, 'cards/list.html', context)
@@ -158,6 +163,35 @@ def background_list(request):
return render(request, 'cards/backgrounds_list.html', {'backgrounds': backgrounds, 'form': form}) return render(request, 'cards/backgrounds_list.html', {'backgrounds': backgrounds, 'form': form})
def backgrounds_download_all(request):
"""Provide a ZIP archive download containing all uploaded background images.
Creates the ZIP in-memory and returns it as an attachment. Skips missing
files gracefully.
"""
backgrounds = BackgroundImage.objects.all()
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
for bg in backgrounds:
if not getattr(bg, 'image', None):
continue
try:
filepath = bg.image.path
except Exception:
# If storage backend doesn't expose a local path, skip.
continue
if not os.path.exists(filepath):
continue
arcname = os.path.basename(bg.image.name) if getattr(bg.image, 'name', None) else f'background-{bg.pk}'
# Prefix with id to avoid duplicate names
arcname = f"{bg.pk}_{arcname}"
zf.write(filepath, arcname)
buffer.seek(0)
resp = HttpResponse(buffer.getvalue(), content_type='application/zip')
resp['Content-Disposition'] = 'attachment; filename=backgrounds.zip'
return resp
def background_create(request): def background_create(request):
if request.method != 'POST': if request.method != 'POST':
return HttpResponseBadRequest('Only POST allowed') return HttpResponseBadRequest('Only POST allowed')
@@ -222,7 +256,8 @@ def cards_overview(request):
qs = Dinosaur.objects.order_by('name') qs = Dinosaur.objects.order_by('name')
# Use a modelformset so the template renders the management form # Use a modelformset so the template renders the management form
formset = DinosaurBulkFormSet(queryset=qs) formset = DinosaurBulkFormSet(queryset=qs)
return render(request, 'cards/bulk_edit.html', {'formset': formset, 'backgrounds': backgrounds}) card_count = qs.count()
return render(request, 'cards/bulk_edit.html', {'formset': formset, 'backgrounds': backgrounds, 'card_count': card_count})
def cards_bulk_update(request): def cards_bulk_update(request):
@@ -243,7 +278,8 @@ def cards_bulk_update(request):
return redirect('cards:overview') return redirect('cards:overview')
# If invalid, re-render the overview with errors # If invalid, re-render the overview with errors
backgrounds = BackgroundImage.objects.all() backgrounds = BackgroundImage.objects.all()
return render(request, 'cards/bulk_edit.html', {'formset': formset, 'backgrounds': backgrounds}) card_count = qs.count()
return render(request, 'cards/bulk_edit.html', {'formset': formset, 'backgrounds': backgrounds, 'card_count': card_count})
def cards_bulk_create_images(request): def cards_bulk_create_images(request):
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB