.
This commit is contained in:
@@ -20,3 +20,17 @@ class DinosaurForm(forms.ModelForm):
|
||||
'intelligence': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100}),
|
||||
'height': forms.NumberInput(attrs={'class': 'input', 'min': 0, 'max': 100}),
|
||||
}
|
||||
|
||||
|
||||
from .models import BackgroundImage
|
||||
|
||||
|
||||
class BackgroundImageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BackgroundImage
|
||||
fields = ['title', 'image', 'is_active']
|
||||
widgets = {
|
||||
'title': forms.TextInput(attrs={'class': 'input', 'placeholder': 'Rocky desert'}),
|
||||
'image': forms.ClearableFileInput(attrs={'class': 'file-input'}),
|
||||
'is_active': forms.CheckboxInput(attrs={'class': 'checkbox'}),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
{% load static %}
|
||||
<form hx-post="{% url 'cards:background_create' %}" hx-target="#backgrounds-list" hx-swap="innerHTML" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label">Title</label>
|
||||
<div class="control">
|
||||
{{ form.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Image</label>
|
||||
<div class="control">
|
||||
<div class="file">
|
||||
<label class="file-label">
|
||||
{{ form.image }}
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">📁</span>
|
||||
<span class="file-label">Choose a file…</span>
|
||||
</span>
|
||||
<span class="file-name" style="margin-left:0.5rem">No file chosen</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">{{ form.is_active }} Set active</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="submit">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Show selected filename in the Bulma file-name span
|
||||
(function(){
|
||||
const form = document.currentScript && document.currentScript.previousElementSibling ? document.currentScript.previousElementSibling : null;
|
||||
// If script is placed at the end of form, walk up to find file input(s)
|
||||
const fileInputs = (form && form.querySelectorAll) ? form.querySelectorAll('input[type=file]') : document.querySelectorAll('input[type=file]');
|
||||
fileInputs.forEach(function(fi){
|
||||
fi.addEventListener('change', function(e){
|
||||
const label = fi.closest('.file').querySelector('.file-name');
|
||||
if(!label) return;
|
||||
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;
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<div id="backgrounds-list">
|
||||
<div class="columns is-multiline">
|
||||
{% for bg in backgrounds %}
|
||||
<div class="column is-3-desktop is-4-tablet is-6-mobile">
|
||||
<article class="box">
|
||||
<div style="height:140px; background-image:url('{{ bg.image.url }}'); background-size:cover; background-position:center; border-radius:4px;"></div>
|
||||
<div style="margin-top:0.5rem">
|
||||
<strong>{{ bg.title }}</strong>
|
||||
<div class="is-size-7">Uploaded {{ bg.uploaded_at }}</div>
|
||||
<div class="buttons" style="margin-top:0.5rem">
|
||||
{% if not bg.is_active %}
|
||||
<button class="button is-small" hx-post="{% url 'cards:background_activate' bg.pk %}" hx-swap="outerHTML" hx-target="#backgrounds-list">Set active</button>
|
||||
{% else %}
|
||||
<span class="tag is-success">Active</span>
|
||||
{% endif %}
|
||||
<button class="button is-small is-danger" hx-post="{% url 'cards:background_delete' bg.pk %}" hx-confirm="Delete this background?" hx-swap="none">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="notification is-warning">No backgrounds yet — upload one!</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
{% extends 'cards/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<h2 class="title is-4">Background images</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
<div id="htmx-background-form">
|
||||
{% include 'cards/_background_form.html' with form=form %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<div id="backgrounds-list">
|
||||
{% include 'cards/_backgrounds_list.html' with backgrounds=backgrounds %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -6,6 +6,9 @@
|
||||
<div class="level-item">
|
||||
<button class="button is-primary" hx-get="{% url 'cards:create' %}" hx-target="#htmx-form" hx-swap="innerHTML">Add Dinosaur</button>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,4 +8,8 @@ urlpatterns = [
|
||||
path('create/', views.card_create, name='create'),
|
||||
path('<int:pk>/edit/', views.card_edit, name='edit'),
|
||||
path('<int:pk>/delete/', views.card_delete, name='delete'),
|
||||
path('backgrounds/', views.background_list, name='backgrounds'),
|
||||
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'),
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@ from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from django.template.loader import render_to_string
|
||||
from .models import Dinosaur, BackgroundImage
|
||||
from .forms import DinosaurForm
|
||||
from .forms import BackgroundImageForm
|
||||
|
||||
|
||||
def is_htmx(request):
|
||||
@@ -75,3 +76,59 @@ def card_delete(request, pk):
|
||||
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')
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 5.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.3 MiB |
@@ -6,6 +6,9 @@
|
||||
<div class="level-item">
|
||||
<button class="button is-primary" hx-get="{% url 'cards:create' %}" hx-target="#htmx-form" hx-swap="innerHTML">Add Dinosaur</button>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<a class="button is-light" href="{% url 'cards:backgrounds' %}">Manage backgrounds</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user