p
This commit is contained in:
@@ -131,19 +131,47 @@
|
|||||||
<div class="field-body">
|
<div class="field-body">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.speed.label }}</label>
|
<label class="label">{{ form.speed.label }}</label>
|
||||||
<div class="control">{{ form.speed }}</div>
|
<div class="control">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.weight.label }}</label>
|
<label class="label">{{ form.weight.label }}</label>
|
||||||
<div class="control">{{ form.weight }}</div>
|
<div class="control">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.intelligence.label }}</label>
|
<label class="label">{{ form.intelligence.label }}</label>
|
||||||
<div class="control">{{ form.intelligence }}</div>
|
<div class="control">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ form.height.label }}</label>
|
<label class="label">{{ form.height.label }}</label>
|
||||||
<div class="control">{{ form.height }}</div>
|
<div class="control">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,13 +2,30 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
|
||||||
<h2 class="title is-4">Cards overview — bulk edit</h2>
|
<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>
|
<p class="subtitle is-6">Edit multiple cards inline and save changes in one go.</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Allow inputs inside the table to wrap on narrow viewports */
|
||||||
|
.table-container .field.has-addons{white-space:normal}
|
||||||
|
.table-container .control.is-expanded{width:100%}
|
||||||
|
.table-container input.input{min-width:100px}
|
||||||
|
.table-container td{padding-left: 0.1rem; padding-right: 0.1rem;}
|
||||||
|
/* Ensure thumbnails don't shrink layout unexpectedly */
|
||||||
|
.bulk-thumb{flex:none}
|
||||||
|
/* Make this page full-bleed: allow the app container to use the full viewport width */
|
||||||
|
.app-container{max-width:none; width:100%; padding-left:0.5rem; padding-right:0.5rem}
|
||||||
|
/* Remove extra section padding so content touches edges if needed */
|
||||||
|
section.section{padding-left:0; padding-right:0}
|
||||||
|
/* Table container: allow horizontal scroll and make table use full available width */
|
||||||
|
.table-container{width:100%; overflow-x:auto}
|
||||||
|
.table-container .table{min-width:1100px}
|
||||||
|
</style>
|
||||||
|
|
||||||
<form method="post" action="{% url 'cards:overview_bulk_update' %}">
|
<form method="post" action="{% url 'cards:overview_bulk_update' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table class="table is-fullwidth is-striped">
|
<div class="table-container">
|
||||||
|
<table class="table is-fullwidth is-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Preview</th>
|
<th>Preview</th>
|
||||||
@@ -35,10 +52,30 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{ form.name }}</td>
|
<td>{{ form.name }}</td>
|
||||||
<td>{{ form.background }}</td>
|
<td>{{ form.background }}</td>
|
||||||
<td style="width:90px">{{ form.speed }}</td>
|
<td style="width:90px">
|
||||||
<td style="width:90px">{{ form.weight }}</td>
|
<div class="field has-addons">
|
||||||
<td style="width:90px">{{ form.height }}</td>
|
<div class="control is-expanded">{{ form.speed }}</div>
|
||||||
<td style="width:90px">{{ form.intelligence }}</td>
|
<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>
|
<td style="width:90px">ID: {{ form.instance.pk }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -61,16 +98,37 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select></div>
|
</select></div>
|
||||||
</td>
|
</td>
|
||||||
<td><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></td>
|
<td style="width:90px">
|
||||||
<td><input class="input" name="weight-{{ card.pk }}" value="{{ card.weight }}"></td>
|
<div class="field has-addons">
|
||||||
<td><input class="input" name="height-{{ card.pk }}" value="{{ card.height }}"></td>
|
<div class="control is-expanded"><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></div>
|
||||||
<td><input class="input" name="intelligence-{{ card.pk }}" value="{{ card.intelligence }}"></td>
|
<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>
|
<td>ID: {{ card.pk }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
@@ -79,6 +137,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+29
-6
@@ -1,6 +1,7 @@
|
|||||||
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 os
|
||||||
from .models import Dinosaur, BackgroundImage
|
from .models import Dinosaur, BackgroundImage
|
||||||
from .forms import DinosaurBulkFormSet
|
from .forms import DinosaurBulkFormSet
|
||||||
from .forms import DinosaurForm
|
from .forms import DinosaurForm
|
||||||
@@ -26,7 +27,16 @@ def card_create(request):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = DinosaurForm(request.POST, request.FILES)
|
form = DinosaurForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
card = form.save()
|
# Save instance with commit=False so we can set a default name
|
||||||
|
# from the uploaded image filename when the name is empty.
|
||||||
|
card = form.save(commit=False)
|
||||||
|
if not card.name:
|
||||||
|
image_file = form.cleaned_data.get('image')
|
||||||
|
if image_file and getattr(image_file, 'name', None):
|
||||||
|
base = os.path.splitext(image_file.name)[0]
|
||||||
|
# replace separators and title-case for a nicer default
|
||||||
|
card.name = base.replace('_', ' ').replace('-', ' ').strip().title()
|
||||||
|
card.save()
|
||||||
if is_htmx(request):
|
if is_htmx(request):
|
||||||
card_html = render_to_string('cards/_card_item.html', {'card': card, 'background': background}, 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
|
# Also return an out-of-band fragment to replace/clear the form container
|
||||||
@@ -53,7 +63,15 @@ def card_edit(request, pk):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = DinosaurForm(request.POST, request.FILES, instance=card)
|
form = DinosaurForm(request.POST, request.FILES, instance=card)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
card = form.save()
|
# Save with commit=False to allow setting a default name from an
|
||||||
|
# uploaded image if the instance has no name.
|
||||||
|
card = form.save(commit=False)
|
||||||
|
if not card.name:
|
||||||
|
image_file = form.cleaned_data.get('image')
|
||||||
|
if image_file and getattr(image_file, 'name', None):
|
||||||
|
base = os.path.splitext(image_file.name)[0]
|
||||||
|
card.name = base.replace('_', ' ').replace('-', ' ').strip().title()
|
||||||
|
card.save()
|
||||||
if is_htmx(request):
|
if is_htmx(request):
|
||||||
card_html = render_to_string('cards/_card_item.html', {'card': card, 'background': background}, 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
|
# Clear the form container via OOB so the edit form disappears after successful save
|
||||||
@@ -150,17 +168,22 @@ def cards_overview(request):
|
|||||||
"""Overview page showing all cards in a table for quick edits."""
|
"""Overview page showing all cards in a table for quick edits."""
|
||||||
backgrounds = BackgroundImage.objects.all()
|
backgrounds = BackgroundImage.objects.all()
|
||||||
qs = Dinosaur.objects.order_by('name')
|
qs = Dinosaur.objects.order_by('name')
|
||||||
formset = None
|
# Use a modelformset so the template renders the management form
|
||||||
return render(request, 'cards/bulk_edit.html', {'cards': qs, 'backgrounds': backgrounds})
|
formset = DinosaurBulkFormSet(queryset=qs)
|
||||||
|
return render(request, 'cards/bulk_edit.html', {'formset': formset, 'backgrounds': backgrounds})
|
||||||
|
|
||||||
|
|
||||||
def cards_bulk_update(request):
|
def cards_bulk_update(request):
|
||||||
"""Handle POST from the bulk edit formset and save changes."""
|
"""Handle POST from the bulk edit formset and save changes."""
|
||||||
if request.method != 'POST':
|
if request.method != 'POST':
|
||||||
return HttpResponseBadRequest('Only POST allowed')
|
# If someone visits the bulk-update URL with GET, redirect them back
|
||||||
|
# to the overview page instead of returning a 400 Bad Request.
|
||||||
|
return redirect('cards:overview')
|
||||||
formset = None
|
formset = None
|
||||||
|
qs = Dinosaur.objects.order_by('name')
|
||||||
try:
|
try:
|
||||||
formset = DinosaurBulkFormSet(request.POST)
|
# Pass the same queryset so management/initial forms align with the rendered formset
|
||||||
|
formset = DinosaurBulkFormSet(request.POST, queryset=qs)
|
||||||
except Exception:
|
except Exception:
|
||||||
return HttpResponseBadRequest('Invalid submission')
|
return HttpResponseBadRequest('Invalid submission')
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -112,6 +112,17 @@
|
|||||||
if(!label) return;
|
if(!label) return;
|
||||||
if(fi.files.length === 0) label.textContent = 'No file chosen';
|
if(fi.files.length === 0) label.textContent = 'No file chosen';
|
||||||
else label.textContent = fi.files.length > 1 ? fi.files.length + ' files selected' : fi.files[0].name;
|
else label.textContent = fi.files.length > 1 ? fi.files.length + ' files selected' : fi.files[0].name;
|
||||||
|
try {
|
||||||
|
const nameInput = formRoot.querySelector('input[name="name"]');
|
||||||
|
if(nameInput && (!nameInput.value || nameInput.value.trim() === '')){
|
||||||
|
const f = fi.files && fi.files[0];
|
||||||
|
if(f && f.name){
|
||||||
|
const base = f.name.replace(/\.[^/.]+$/, '');
|
||||||
|
const title = base.replace(/[_-]+/g, ' ').split(/\s+/).map(function(w){ return w.length? (w.charAt(0).toUpperCase() + w.slice(1)) : ''; }).join(' ').trim();
|
||||||
|
if(title) nameInput.value = title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(e){ /* ignore UI enhancement errors */ }
|
||||||
});
|
});
|
||||||
const outerLabel = fi.closest('.file').querySelector('.file-label');
|
const outerLabel = fi.closest('.file').querySelector('.file-label');
|
||||||
if(outerLabel && !outerLabel.contains(fi)){
|
if(outerLabel && !outerLabel.contains(fi)){
|
||||||
|
|||||||
@@ -2,13 +2,29 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
|
||||||
<h2 class="title is-4">Cards overview — bulk edit</h2>
|
<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>
|
<p class="subtitle is-6">Edit multiple cards inline and save changes in one go.</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Allow inputs inside the table to wrap on narrow viewports */
|
||||||
|
.table-container .field.has-addons{white-space:normal}
|
||||||
|
.table-container .control.is-expanded{width:100%}
|
||||||
|
.table-container input.input{min-width:50px}
|
||||||
|
/* Ensure thumbnails don't shrink layout unexpectedly */
|
||||||
|
.bulk-thumb{flex:none}
|
||||||
|
/* Make this page full-bleed: allow the app container to use the full viewport width */
|
||||||
|
.app-container{max-width:none; width:100%; padding-left:0.5rem; padding-right:0.5rem}
|
||||||
|
/* Remove extra section padding so content touches edges if needed */
|
||||||
|
section.section{padding-left:0; padding-right:0}
|
||||||
|
/* Table container: allow horizontal scroll and make table use full available width */
|
||||||
|
.table-container{width:100%; overflow-x:auto}
|
||||||
|
.table-container .table{min-width:1100px}
|
||||||
|
</style>
|
||||||
|
|
||||||
<form method="post" action="{% url 'cards:overview_bulk_update' %}">
|
<form method="post" action="{% url 'cards:overview_bulk_update' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table class="table is-fullwidth is-striped">
|
<div class="table-container">
|
||||||
|
<table class="table is-fullwidth is-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Preview</th>
|
<th>Preview</th>
|
||||||
@@ -61,16 +77,37 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select></div>
|
</select></div>
|
||||||
</td>
|
</td>
|
||||||
<td><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></td>
|
<td style="width:90px">
|
||||||
<td><input class="input" name="weight-{{ card.pk }}" value="{{ card.weight }}"></td>
|
<div class="field has-addons">
|
||||||
<td><input class="input" name="height-{{ card.pk }}" value="{{ card.height }}"></td>
|
<div class="control is-expanded"><input class="input" name="speed-{{ card.pk }}" value="{{ card.speed }}"></div>
|
||||||
<td><input class="input" name="intelligence-{{ card.pk }}" value="{{ card.intelligence }}"></td>
|
<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>
|
<td>ID: {{ card.pk }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
@@ -79,6 +116,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user