This commit is contained in:
Ross
2025-12-05 12:08:08 +00:00
parent d55cd821b5
commit 78240090f1
9 changed files with 227 additions and 0 deletions
+19
View File
@@ -39,3 +39,22 @@ class BackgroundImageForm(forms.ModelForm):
'image': forms.ClearableFileInput(attrs={'class': 'file-input'}), 'image': forms.ClearableFileInput(attrs={'class': 'file-input'}),
'is_active': forms.CheckboxInput(attrs={'class': 'checkbox'}), 'is_active': forms.CheckboxInput(attrs={'class': 'checkbox'}),
} }
from django.forms import modelformset_factory
# A simplified form used in the bulk edit table (only a few editable fields)
class DinosaurBulkForm(forms.ModelForm):
class Meta:
model = Dinosaur
fields = ['name', 'background', 'speed', 'weight', 'height', 'intelligence']
widgets = {
'name': forms.TextInput(attrs={'class': 'input'}),
'background': forms.Select(attrs={'class': 'input'}),
'speed': forms.NumberInput(attrs={'class': 'input', 'min': 0}),
'weight': forms.NumberInput(attrs={'class': 'input', 'min': 0}),
'height': forms.NumberInput(attrs={'class': 'input', 'step': '0.1'}),
'intelligence': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100}),
}
DinosaurBulkFormSet = modelformset_factory(Dinosaur, form=DinosaurBulkForm, extra=0)
+3
View File
@@ -63,6 +63,9 @@
.background-picker .bg-option img{width:100%;height:100%;object-fit:cover;display:block;border-radius:2px} .background-picker .bg-option img{width:100%;height:100%;object-fit:cover;display:block;border-radius:2px}
.background-picker .bg-option.selected{outline:2px solid var(--accent);box-shadow:0 2px 6px rgba(0,0,0,0.08)} .background-picker .bg-option.selected{outline:2px solid var(--accent);box-shadow:0 2px 6px rgba(0,0,0,0.08)}
.background-picker .bg-option[role="button"]{font-size:0.8rem;padding:6px 8px} .background-picker .bg-option[role="button"]{font-size:0.8rem;padding:6px 8px}
/* Bulk edit thumbnails */
.bulk-thumb{width:48px;height:48px;object-fit:cover;border-radius:6px;border:1px solid rgba(0,0,0,0.06);display:inline-block}
.bulk-thumb.empty{background:rgba(0,0,0,0.04);width:48px;height:48px;border-radius:6px}
.toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem} .toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem}
.toptrump-card .card-body{padding:0.75rem} .toptrump-card .card-body{padding:0.75rem}
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0} .toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
+84
View File
@@ -0,0 +1,84 @@
{% extends "cards/base.html" %}
{% block content %}
<section class="section">
<div class="container">
<h2 class="title is-4">Cards overview — bulk edit</h2>
<p class="subtitle is-6">Edit multiple cards inline and save changes in one go.</p>
<form method="post" action="{% url 'cards:overview_bulk_update' %}">
{% csrf_token %}
<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 %}
<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">{{ form.speed }}</td>
<td style="width:90px">{{ form.weight }}</td>
<td style="width:90px">{{ form.height }}</td>
<td style="width:90px">{{ form.intelligence }}</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><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></td>
<td><input class="input" name="weight-{{ card.pk }}" value="{{ card.weight }}"></td>
<td><input class="input" name="height-{{ card.pk }}" value="{{ card.height }}"></td>
<td><input class="input" name="intelligence-{{ card.pk }}" value="{{ card.intelligence }}"></td>
<td>ID: {{ card.pk }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
<div class="field">
<div class="control">
<button class="button is-primary" type="submit">Save changes</button>
<a class="button is-light" href="{% url 'cards:list' %}">Back to list</a>
</div>
</div>
</form>
</div>
</section>
{% endblock %}
+3
View File
@@ -9,6 +9,9 @@
<div class="level-item"> <div class="level-item">
<a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a> <a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a>
</div> </div>
<div class="level-item">
<a class="button is-light" href="{% url 'cards:overview' %}">Bulk edit</a>
</div>
</div> </div>
</div> </div>
+2
View File
@@ -13,4 +13,6 @@ urlpatterns = [
path('backgrounds/create/', views.background_create, name='background_create'), path('backgrounds/create/', views.background_create, name='background_create'),
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/bulk-update/', views.cards_bulk_update, name='overview_bulk_update'),
] ]
+26
View File
@@ -2,6 +2,7 @@ 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
from .models import Dinosaur, BackgroundImage from .models import Dinosaur, BackgroundImage
from .forms import DinosaurBulkFormSet
from .forms import DinosaurForm from .forms import DinosaurForm
from .forms import BackgroundImageForm from .forms import BackgroundImageForm
@@ -143,3 +144,28 @@ def preview_card(request, pk):
if is_htmx(request): if is_htmx(request):
return render(request, 'cards/_card_preview.html', {'card': card, 'background': background}) return render(request, 'cards/_card_preview.html', {'card': card, 'background': background})
return render(request, 'cards/preview.html', {'card': card, 'background': background}) return render(request, 'cards/preview.html', {'card': card, 'background': background})
def cards_overview(request):
"""Overview page showing all cards in a table for quick edits."""
backgrounds = BackgroundImage.objects.all()
qs = Dinosaur.objects.order_by('name')
formset = None
return render(request, 'cards/bulk_edit.html', {'cards': qs, 'backgrounds': backgrounds})
def cards_bulk_update(request):
"""Handle POST from the bulk edit formset and save changes."""
if request.method != 'POST':
return HttpResponseBadRequest('Only POST allowed')
formset = None
try:
formset = DinosaurBulkFormSet(request.POST)
except Exception:
return HttpResponseBadRequest('Invalid submission')
if formset.is_valid():
formset.save()
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})
+3
View File
@@ -56,6 +56,9 @@
.background-picker .bg-option img{max-width:100%;max-height:100%;display:block;border-radius:2px} .background-picker .bg-option img{max-width:100%;max-height:100%;display:block;border-radius:2px}
.background-picker .bg-option.selected{outline:2px solid var(--accent);box-shadow:0 2px 6px rgba(0,0,0,0.08)} .background-picker .bg-option.selected{outline:2px solid var(--accent);box-shadow:0 2px 6px rgba(0,0,0,0.08)}
.background-picker .bg-option[role="button"]{font-size:0.8rem;padding:6px 8px} .background-picker .bg-option[role="button"]{font-size:0.8rem;padding:6px 8px}
/* Bulk edit thumbnails */
.bulk-thumb{width:48px;height:48px;object-fit:cover;border-radius:6px;border:1px solid rgba(0,0,0,0.06);display:inline-block}
.bulk-thumb.empty{background:rgba(0,0,0,0.04);width:48px;height:48px;border-radius:6px}
.toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem} .toptrump-card .title-band{background:var(--accent);color:#072027;padding:0.6rem 0.75rem;text-align:center;font-weight:700;font-size:1.05rem}
.toptrump-card .card-body{padding:0.75rem} .toptrump-card .card-body{padding:0.75rem}
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0} .toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
+84
View File
@@ -0,0 +1,84 @@
{% extends "cards/base.html" %}
{% block content %}
<section class="section">
<div class="container">
<h2 class="title is-4">Cards overview — bulk edit</h2>
<p class="subtitle is-6">Edit multiple cards inline and save changes in one go.</p>
<form method="post" action="{% url 'cards:overview_bulk_update' %}">
{% csrf_token %}
<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 %}
<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">{{ form.speed }}</td>
<td style="width:90px">{{ form.weight }}</td>
<td style="width:90px">{{ form.height }}</td>
<td style="width:90px">{{ form.intelligence }}</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><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></td>
<td><input class="input" name="weight-{{ card.pk }}" value="{{ card.weight }}"></td>
<td><input class="input" name="height-{{ card.pk }}" value="{{ card.height }}"></td>
<td><input class="input" name="intelligence-{{ card.pk }}" value="{{ card.intelligence }}"></td>
<td>ID: {{ card.pk }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
<div class="field">
<div class="control">
<button class="button is-primary" type="submit">Save changes</button>
<a class="button is-light" href="{% url 'cards:list' %}">Back to list</a>
</div>
</div>
</form>
</div>
</section>
{% endblock %}
+3
View File
@@ -9,6 +9,9 @@
<div class="level-item"> <div class="level-item">
<a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a> <a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a>
</div> </div>
<div class="level-item">
<a class="button is-light" href="{% url 'cards:overview' %}">Bulk edit</a>
</div>
</div> </div>
</div> </div>