Files
toptrumps/cards/views.py
T
Ross bb8298b18c p
2025-12-05 13:33:34 +00:00

195 lines
9.1 KiB
Python

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 os
from .models import Dinosaur, BackgroundImage
from .forms import DinosaurBulkFormSet
from .forms import DinosaurForm
from .forms import BackgroundImageForm
def is_htmx(request):
return request.headers.get('HX-Request') == 'true'
def card_list(request):
cards = Dinosaur.objects.all()
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
if is_htmx(request):
return render(request, 'cards/_cards_list.html', {'cards': cards, 'background': background})
return render(request, 'cards/list.html', {'cards': cards, 'background': background})
def card_create(request):
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
backgrounds = BackgroundImage.objects.all()
if request.method == 'POST':
form = DinosaurForm(request.POST, request.FILES)
if form.is_valid():
# 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):
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
form_html = render_to_string('cards/_form.html', {'form': DinosaurForm(), 'background': background, 'backgrounds': backgrounds}, request=request)
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
return HttpResponse(card_html + oob)
return redirect('cards:list')
else:
if is_htmx(request):
# Return the form as an out-of-band fragment so it updates the form container
form_html = render_to_string('cards/_form.html', {'form': form, 'background': background, 'backgrounds': backgrounds}, request=request)
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
return HttpResponse(oob)
else:
form = DinosaurForm()
return render(request, 'cards/_form.html', {'form': form, 'background': background, 'backgrounds': backgrounds})
def card_edit(request, pk):
card = get_object_or_404(Dinosaur, pk=pk)
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
backgrounds = BackgroundImage.objects.all()
if request.method == 'POST':
form = DinosaurForm(request.POST, request.FILES, instance=card)
if form.is_valid():
# 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):
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
oob = '<div id="htmx-form" hx-swap-oob="true"></div>'
return HttpResponse(card_html + oob)
return redirect('cards:list')
else:
if is_htmx(request):
form_html = render_to_string('cards/_form.html', {'form': form, 'card': card, 'background': background, 'backgrounds': backgrounds}, request=request)
oob = f'<div id="htmx-form" hx-swap-oob="true">{form_html}</div>'
return HttpResponse(oob)
else:
form = DinosaurForm(instance=card)
return render(request, 'cards/_form.html', {'form': form, 'card': card, 'background': background, 'backgrounds': backgrounds})
def card_delete(request, pk):
card = get_object_or_404(Dinosaur, pk=pk)
if request.method == 'POST':
card.delete()
if is_htmx(request):
return HttpResponse(status=204)
return redirect('cards:list')
return HttpResponseBadRequest('Only POST allowed')
def background_list(request):
"""Show existing backgrounds and provide a form to upload a new one."""
backgrounds = BackgroundImage.objects.all()
form = BackgroundImageForm()
if is_htmx(request):
return render(request, 'cards/_backgrounds_list.html', {'backgrounds': backgrounds, 'form': form})
return render(request, 'cards/backgrounds_list.html', {'backgrounds': backgrounds, 'form': form})
def background_create(request):
if request.method != 'POST':
return HttpResponseBadRequest('Only POST allowed')
form = BackgroundImageForm(request.POST, request.FILES)
if form.is_valid():
bg = form.save()
# If marked active, unset others
if bg.is_active:
BackgroundImage.objects.exclude(pk=bg.pk).update(is_active=False)
if is_htmx(request):
# return the updated list fragment and clear the form OOB
list_html = render_to_string('cards/_backgrounds_list.html', {'backgrounds': BackgroundImage.objects.all(), 'form': BackgroundImageForm()}, request=request)
oob = f'<div id="htmx-background-form" hx-swap-oob="true">{render_to_string("cards/_background_form.html", {"form": BackgroundImageForm()}, request=request)}</div>'
return HttpResponse(list_html + oob)
return redirect('cards:backgrounds')
else:
if is_htmx(request):
form_html = render_to_string('cards/_background_form.html', {'form': form}, request=request)
oob = f'<div id="htmx-background-form" hx-swap-oob="true">{form_html}</div>'
return HttpResponse(oob)
return render(request, 'cards/backgrounds_list.html', {'backgrounds': BackgroundImage.objects.all(), 'form': form})
def background_activate(request, pk):
if request.method != 'POST':
return HttpResponseBadRequest('Only POST allowed')
bg = get_object_or_404(BackgroundImage, pk=pk)
BackgroundImage.objects.update(is_active=False)
bg.is_active = True
bg.save()
if is_htmx(request):
list_html = render_to_string('cards/_backgrounds_list.html', {'backgrounds': BackgroundImage.objects.all(), 'form': BackgroundImageForm()}, request=request)
return HttpResponse(list_html)
return redirect('cards:backgrounds')
def background_delete(request, pk):
if request.method != 'POST':
return HttpResponseBadRequest('Only POST allowed')
bg = get_object_or_404(BackgroundImage, pk=pk)
bg.delete()
if is_htmx(request):
return HttpResponse(status=204)
return redirect('cards:backgrounds')
def preview_card(request, pk):
"""Render a full preview of a card. HTMX loads the modal fragment into #modal-root."""
card = get_object_or_404(Dinosaur, pk=pk)
background = BackgroundImage.get_active() if BackgroundImage.objects.exists() else None
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')
# 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})
def cards_bulk_update(request):
"""Handle POST from the bulk edit formset and save changes."""
if request.method != 'POST':
# 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
qs = Dinosaur.objects.order_by('name')
try:
# Pass the same queryset so management/initial forms align with the rendered formset
formset = DinosaurBulkFormSet(request.POST, queryset=qs)
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})