diff --git a/argocd/configmap.yaml b/argocd/configmap.yaml
index c4b8492..958ac8b 100644
--- a/argocd/configmap.yaml
+++ b/argocd/configmap.yaml
@@ -18,4 +18,4 @@ data:
LOGIN_REDIRECT_URL: "index"
LOGOUT_REDIRECT_URL: "login"
GUNICORN_OPTS: "--access-logfile -"
- IMAGE_TAG: "0.064"
+ IMAGE_TAG: "0.066"
diff --git a/argocd/deployment.yaml b/argocd/deployment.yaml
index b957bcc..5f38df7 100644
--- a/argocd/deployment.yaml
+++ b/argocd/deployment.yaml
@@ -27,7 +27,7 @@ spec:
mountPath: /data
containers:
- name: web
- image: git.baumann.gr/adebaumann/labhelper:0.065
+ image: git.baumann.gr/adebaumann/labhelper:0.066
imagePullPolicy: Always
ports:
- containerPort: 8000
diff --git a/boxes/admin.py b/boxes/admin.py
index 73b16c5..10d7fc6 100644
--- a/boxes/admin.py
+++ b/boxes/admin.py
@@ -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):
diff --git a/boxes/migrations/0011_alter_box_options_box_sort_order.py b/boxes/migrations/0011_alter_box_options_box_sort_order.py
new file mode 100644
index 0000000..30d9718
--- /dev/null
+++ b/boxes/migrations/0011_alter_box_options_box_sort_order.py
@@ -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'),
+ ),
+ ]
diff --git a/boxes/templates/admin/boxes/box/change_list.html b/boxes/templates/admin/boxes/box/change_list.html
new file mode 100644
index 0000000..e1210d2
--- /dev/null
+++ b/boxes/templates/admin/boxes/box/change_list.html
@@ -0,0 +1,126 @@
+{% extends "admin/change_list.html" %}
+
+{% block extrahead %}
+{{ block.super }}
+
+{% endblock %}
+
+{% block result_list %}
+{{ block.super }}
+
+{% endblock %}
diff --git a/labhelper/settings.py b/labhelper/settings.py
index b48016e..8ba9855 100644
--- a/labhelper/settings.py
+++ b/labhelper/settings.py
@@ -39,7 +39,6 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
- 'adminsortable2',
'mptt',
'django_mptt_admin',
'sorl.thumbnail',