views.py
from django.views.generic import CreateView, ListView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from .models import Product
from .forms import ProductForm
class ProductDeleteView(DeleteView):
model = Product
success_url = reverse_lazy("product_list")
def post(self, request, *args, **kwargs):
# Handle both POST and DELETE methods (HTMX sends DELETE as POST with _method=DELETE)
return self.delete(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
# Get the object
self.object = self.get_object()
product_id = self.object.id
# Delete the object
self.object.delete()
# Return appropriate response based on request type
if request.htmx:
# For HTMX requests, return empty response to remove the row
return HttpResponse("")
else:
# For non-HTMX requests, redirect to success URL
return HttpResponseRedirect(self.get_success_url())urls.py
from django.urls import path
from .views import (
ProductDeleteView
)
urlpatterns = [
path("delete/<int:pk>/", ProductDeleteView.as_view(), name="product_delete"),
]_product_list.html
<div class="overflow-hidden rounded-lg border border-gray-200 shadow-lg">
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900">Product Inventory</h3>
<span class="text-sm text-gray-500">{{ products.count }} items</span>
</div>
</div>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Product</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Price</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
<th class="px-6 py-3"></th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for product in products %}
<tr class="hover:bg-gray-50 transition-colors duration-150">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ product.name }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">${{ product.price }}</div>
</td>
<td class="px-6 py-4">
<div class="text-sm text-gray-500 max-w-xs truncate">{{ product.description }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<!-- Edit Button -->
<button hx-get="{% url 'product_edit' product.pk %}"
hx-target="#main-content"
hx-push-url="true"
class="text-blue-600 hover:text-blue-900 mr-4">
Edit
</button>
<!-- Delete Button - Fixed -->
<button hx-delete="{% url 'product_delete' product.pk %}"
hx-confirm="Are you sure you want to delete this product?"
hx-target="closest tr"
hx-swap="outerHTML"
class="text-red-600 hover:text-red-900">
Delete
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
base.html
<script>
// Ensure HTMX is loaded
document.addEventListener('DOMContentLoaded', function() {
// Add CSRF token to all HTMX requests
document.body.addEventListener('htmx:configRequest', function(event) {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
});
// Debug HTMX events
document.body.addEventListener('htmx:beforeRequest', function(event) {
console.log('HTMX request starting:', event.detail);
});
document.body.addEventListener('htmx:responseError', function(event) {
console.error('HTMX request failed:', event.detail);
});
});
</script>