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
+1 -1
View File
@@ -47,7 +47,7 @@
<div class="stat-label">Intelligence <small class="is-size-7">(score)</small></div>
<div class="stat-value" style="margin-left:8px;font-weight:700">
<div style="display:flex;align-items:center;gap:8px"><div class="is-size-7" style="font-weight:600"><div class="stars">{% for t in card.intelligence_star_types %}<span class="star {{ t }}">{% if t == 'full' %}★{% else %}☆{% endif %}</span>{% endfor %}</div></div>
<div class="is-size-7" style="font-weight:500">(orig: {{ card.intelligence_orig }})</div></div>
<div class="is-size-7" style="font-weight:500">(orig: {{ card.intelligence_orig }})</div></div>
</div>
</div>
<div class="stat-row">
@@ -11,6 +11,9 @@
<div class="level-item">
<a class="button is-light" href="{% url 'cards:list' %}">Back to cards</a>
</div>
<div class="level-item">
<a class="button is-link" href="{% url 'cards:backgrounds_download_all' %}">Download all backgrounds</a>
</div>
</div>
</div>
+105 -105
View File
@@ -1,8 +1,8 @@
{% extends "cards/base.html" %}
{% block content %}
<section class="section">
<h2 class="title is-4">Cards overview — bulk edit</h2>
<section class="section">
<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>
<style>
@@ -39,108 +39,108 @@
{% csrf_token %}
<div class="table-container">
<table class="table is-fullwidth is-striped">
<thead>
<tr>
<th>Preview</th>
<th>Name</th>
<th>Background</th>
<th>Speed</th>
<th>Weight</th>
<th>Height</th>
<th>Intel</th>
<th></th>
</tr>
</thead>
<tbody>
{% if formset %}
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.id }}
<tr>
<td>
{% if form.instance.image %}
<img class="bulk-thumb" src="{{ form.instance.image.url }}" alt="{{ form.instance.name }}">
{% else %}
<div class="bulk-thumb empty"></div>
{% endif %}
</td>
<td>{{ form.name }}</td>
<td>{{ form.background }}</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.speed }}</div>
<div class="control"><a class="button is-static">km/h</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.weight }}</div>
<div class="control"><a class="button is-static">kg</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.height }}</div>
<div class="control"><a class="button is-static">m</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.intelligence }}</div>
<div class="control"><a class="button is-static">score</a></div>
</div>
</td>
<td style="width:90px">ID: {{ form.instance.pk }}</td>
</tr>
{% endfor %}
{% else %}
{% for card in cards %}
<tr>
<td>
{% if card.image %}
<img class="bulk-thumb" src="{{ card.image.url }}" alt="{{ card.name }}">
{% else %}
<div class="bulk-thumb empty"></div>
{% endif %}
</td>
<td><input class="input" name="name-{{ card.pk }}" value="{{ card.name }}"></td>
<td>
<div class="select"><select name="background-{{ card.pk }}">
<option value="">Site default</option>
{% for bg in backgrounds %}
<option value="{{ bg.pk }}"{% if card.background and card.background.pk == bg.pk %} selected{% endif %}>{{ bg.title|default:'Background' }}</option>
{% endfor %}
</select></div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></div>
<div class="control"><a class="button is-static">km/h</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="weight-{{ card.pk }}" value="{{ card.weight }}"></div>
<div class="control"><a class="button is-static">kg</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="height-{{ card.pk }}" value="{{ card.height }}"></div>
<div class="control"><a class="button is-static">m</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="intelligence-{{ card.pk }}" value="{{ card.intelligence }}"></div>
<div class="control"><a class="button is-static">score</a></div>
</div>
</td>
<td>ID: {{ card.pk }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
<thead>
<tr>
<th>Preview</th>
<th>Name</th>
<th>Background</th>
<th>Speed</th>
<th>Weight</th>
<th>Height</th>
<th>Intel</th>
<th></th>
</tr>
</thead>
<tbody>
{% if formset %}
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.id }}
<tr>
<td>
{% if form.instance.image %}
<img class="bulk-thumb" src="{{ form.instance.image.url }}" alt="{{ form.instance.name }}">
{% else %}
<div class="bulk-thumb empty"></div>
{% endif %}
</td>
<td>{{ form.name }}</td>
<td>{{ form.background }}</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.speed }}</div>
<div class="control"><a class="button is-static">km/h</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.weight }}</div>
<div class="control"><a class="button is-static">kg</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.height }}</div>
<div class="control"><a class="button is-static">m</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded">{{ form.intelligence }}</div>
<div class="control"><a class="button is-static">score</a></div>
</div>
</td>
<td style="width:90px">ID: {{ form.instance.pk }}</td>
</tr>
{% endfor %}
{% else %}
{% for card in cards %}
<tr>
<td>
{% if card.image %}
<img class="bulk-thumb" src="{{ card.image.url }}" alt="{{ card.name }}">
{% else %}
<div class="bulk-thumb empty"></div>
{% endif %}
</td>
<td><input class="input" name="name-{{ card.pk }}" value="{{ card.name }}"></td>
<td>
<div class="select"><select name="background-{{ card.pk }}">
<option value="">Site default</option>
{% for bg in backgrounds %}
<option value="{{ bg.pk }}"{% if card.background and card.background.pk == bg.pk %} selected{% endif %}>{{ bg.title|default:'Background' }}</option>
{% endfor %}
</select></div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></div>
<div class="control"><a class="button is-static">km/h</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="weight-{{ card.pk }}" value="{{ card.weight }}"></div>
<div class="control"><a class="button is-static">kg</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="height-{{ card.pk }}" value="{{ card.height }}"></div>
<div class="control"><a class="button is-static">m</a></div>
</div>
</td>
<td style="width:90px">
<div class="field has-addons">
<div class="control is-expanded"><input class="input" name="intelligence-{{ card.pk }}" value="{{ card.intelligence }}"></div>
<div class="control"><a class="button is-static">score</a></div>
</div>
</td>
<td>ID: {{ card.pk }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
@@ -151,5 +151,5 @@
</div>
</div>
</form>
</section>
</section>
{% endblock %}
+5
View File
@@ -17,6 +17,11 @@
</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>
+1
View File
@@ -11,6 +11,7 @@ urlpatterns = [
path('preview/<int:pk>/', views.preview_card, name='preview'),
path('backgrounds/', views.background_list, name='backgrounds'),
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>/delete/', views.background_delete, name='background_delete'),
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.http import HttpResponse, HttpResponseBadRequest
from django.template.loader import render_to_string
import io
import zipfile
import os
from .models import Dinosaur, BackgroundImage
from .forms import DinosaurBulkFormSet
@@ -62,6 +64,9 @@ def card_list(request):
'current_order': order,
}
# Total (or filtered) card count for UI summary
context['card_count'] = qs.count()
if is_htmx(request):
return render(request, 'cards/_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})
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):
if request.method != 'POST':
return HttpResponseBadRequest('Only POST allowed')
@@ -222,7 +256,8 @@ def cards_overview(request):
qs = Dinosaur.objects.order_by('name')
# Use a modelformset so the template renders the management form
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):
@@ -243,7 +278,8 @@ def cards_bulk_update(request):
return redirect('cards:overview')
# If invalid, re-render the overview with errors
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):
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB