.
This commit is contained in:
@@ -39,3 +39,22 @@ class BackgroundImageForm(forms.ModelForm):
|
||||
'image': forms.ClearableFileInput(attrs={'class': 'file-input'}),
|
||||
'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)
|
||||
|
||||
@@ -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.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}
|
||||
/* 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 .card-body{padding:0.75rem}
|
||||
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -9,6 +9,9 @@
|
||||
<div class="level-item">
|
||||
<a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<a class="button is-light" href="{% url 'cards:overview' %}">Bulk edit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,4 +13,6 @@ urlpatterns = [
|
||||
path('backgrounds/create/', views.background_create, name='background_create'),
|
||||
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'),
|
||||
path('overview/bulk-update/', views.cards_bulk_update, name='overview_bulk_update'),
|
||||
]
|
||||
|
||||
@@ -2,6 +2,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, BackgroundImage
|
||||
from .forms import DinosaurBulkFormSet
|
||||
from .forms import DinosaurForm
|
||||
from .forms import BackgroundImageForm
|
||||
|
||||
@@ -143,3 +144,28 @@ def preview_card(request, pk):
|
||||
if is_htmx(request):
|
||||
return render(request, 'cards/_card_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})
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
.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[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 .card-body{padding:0.75rem}
|
||||
.toptrump-card .stat-row{display:flex;align-items:center;gap:0.75rem;padding:0.25rem 0}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -9,6 +9,9 @@
|
||||
<div class="level-item">
|
||||
<a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<a class="button is-light" href="{% url 'cards:overview' %}">Bulk edit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user