Box sorting works
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
from adminsortable2.admin import SortableAdminMixin
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import SimpleListFilter
|
||||
from django.http import JsonResponse
|
||||
from django.urls import path
|
||||
from django.utils.html import format_html
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from .models import Box, BoxType, Facet, Tag, Thing, ThingFile, ThingLink
|
||||
|
||||
@@ -48,12 +52,37 @@ class BoxTypeAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Box)
|
||||
class BoxAdmin(SortableAdminMixin, admin.ModelAdmin):
|
||||
class BoxAdmin(admin.ModelAdmin):
|
||||
"""Admin configuration for Box model."""
|
||||
|
||||
list_display = ('id', 'box_type')
|
||||
ordering = ['sort_order']
|
||||
list_display = ('id', 'box_type', 'sort_order')
|
||||
list_filter = ('box_type',)
|
||||
search_fields = ('id',)
|
||||
change_list_template = 'admin/boxes/box/change_list.html'
|
||||
|
||||
def get_urls(self):
|
||||
urls = super().get_urls()
|
||||
custom_urls = [
|
||||
path('reorder/', self.admin_site.admin_view(self.reorder_view), name='boxes_box_reorder'),
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def reorder_view(self, request):
|
||||
"""Handle AJAX reorder requests."""
|
||||
if request.method != 'POST':
|
||||
return JsonResponse({'error': 'POST required'}, status=405)
|
||||
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
order = data.get('order', [])
|
||||
|
||||
for index, pk in enumerate(order):
|
||||
Box.objects.filter(pk=pk).update(sort_order=index)
|
||||
|
||||
return JsonResponse({'status': 'ok'})
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
return JsonResponse({'error': str(e)}, status=400)
|
||||
|
||||
|
||||
class ThingFileInline(admin.TabularInline):
|
||||
|
||||
22
boxes/migrations/0011_alter_box_options_box_sort_order.py
Normal file
22
boxes/migrations/0011_alter_box_options_box_sort_order.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.2.9 on 2026-01-19 23:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxes', '0010_remove_thingtype'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='box',
|
||||
options={'ordering': ['sort_order'], 'verbose_name_plural': 'boxes'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='box',
|
||||
name='sort_order',
|
||||
field=models.PositiveIntegerField(db_index=True, default=0, help_text='Order in which boxes are displayed'),
|
||||
),
|
||||
]
|
||||
126
boxes/templates/admin/boxes/box/change_list.html
Normal file
126
boxes/templates/admin/boxes/box/change_list.html
Normal file
@@ -0,0 +1,126 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
#result_list tbody tr {
|
||||
cursor: move;
|
||||
}
|
||||
#result_list tbody tr.dragging {
|
||||
opacity: 0.5;
|
||||
background: #ffffd0;
|
||||
}
|
||||
#result_list tbody tr.drag-over {
|
||||
border-top: 2px solid #417690;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block result_list %}
|
||||
{{ block.super }}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tbody = document.querySelector('#result_list tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
let draggedRow = null;
|
||||
|
||||
tbody.querySelectorAll('tr').forEach(row => {
|
||||
row.draggable = true;
|
||||
|
||||
row.addEventListener('dragstart', function(e) {
|
||||
draggedRow = this;
|
||||
this.classList.add('dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
});
|
||||
|
||||
row.addEventListener('dragend', function() {
|
||||
this.classList.remove('dragging');
|
||||
tbody.querySelectorAll('tr').forEach(r => r.classList.remove('drag-over'));
|
||||
draggedRow = null;
|
||||
});
|
||||
|
||||
row.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
if (this !== draggedRow) {
|
||||
this.classList.add('drag-over');
|
||||
}
|
||||
});
|
||||
|
||||
row.addEventListener('dragleave', function() {
|
||||
this.classList.remove('drag-over');
|
||||
});
|
||||
|
||||
row.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
this.classList.remove('drag-over');
|
||||
|
||||
if (draggedRow && this !== draggedRow) {
|
||||
const allRows = Array.from(tbody.querySelectorAll('tr'));
|
||||
const draggedIndex = allRows.indexOf(draggedRow);
|
||||
const targetIndex = allRows.indexOf(this);
|
||||
|
||||
if (draggedIndex < targetIndex) {
|
||||
this.parentNode.insertBefore(draggedRow, this.nextSibling);
|
||||
} else {
|
||||
this.parentNode.insertBefore(draggedRow, this);
|
||||
}
|
||||
|
||||
saveOrder();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function saveOrder() {
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
const order = [];
|
||||
|
||||
rows.forEach(row => {
|
||||
// Use the action checkbox which contains the PK
|
||||
const checkbox = row.querySelector('input[name="_selected_action"]');
|
||||
if (checkbox) {
|
||||
order.push(checkbox.value);
|
||||
}
|
||||
});
|
||||
|
||||
fetch('{% url "admin:boxes_box_reorder" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || getCookie('csrftoken')
|
||||
},
|
||||
body: JSON.stringify({ order: order })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'ok') {
|
||||
window.location.reload();
|
||||
} else {
|
||||
console.error('Reorder failed:', data.error);
|
||||
alert('Failed to save order');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Reorder error:', error);
|
||||
alert('Failed to save order');
|
||||
});
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user