From 5b0d121534d6e8f4be321d09433b9c1138a67e74 Mon Sep 17 00:00:00 2001 From: Seeeeeyo Date: Fri, 12 Jun 2026 17:25:19 -0600 Subject: [PATCH 1/3] Improve admin session/trial views and expose trial lidar flag in API. Add session name and isMono to session admin, lidar column on trials, expanded search fields, and hasLidarData on trial API responses. Co-authored-by: Cursor --- mcserver/admin.py | 38 +++++++++++++++++++++++++++++++------- mcserver/serializers.py | 35 +++++++++++++++++++++++++++++++---- mcserver/views.py | 8 +++++--- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/mcserver/admin.py b/mcserver/admin.py index 01f8cc4..2f1e2d4 100644 --- a/mcserver/admin.py +++ b/mcserver/admin.py @@ -20,6 +20,7 @@ from django.contrib.auth.models import Group from django.contrib.auth.admin import UserAdmin, GroupAdmin from django.contrib.admin.models import LogEntry +from django.db.models import Exists, OuterRef from datetime import timedelta @@ -54,17 +55,24 @@ class TrialInline(admin.TabularInline): @admin.register(Session) class SessionAdmin(admin.ModelAdmin): list_display = ( - 'id', 'user', 'subject', - 'public', + 'id', 'user', 'subject', 'session_name', + 'public', 'isMono', 'created_at', 'updated_at', 'server', 'status', 'status_changed', - 'trashed', 'trashed_at', 'isMono', + 'trashed', 'trashed_at', ) raw_id_fields = ('user', 'subject') - search_fields = ['id', 'user__username', "subject__name"] + search_fields = ['id', 'user__username', 'subject__name', 'meta__sessionName', 'trial__name'] inlines = [TrialInline] actions = ['set_subject'] + def session_name(self, obj): + if obj.meta: + return obj.meta.get('sessionName', '') + return '' + + session_name.short_description = 'Session name' + def set_subject(self, request, queryset): from .forms import SubjectSelectForm @@ -96,12 +104,13 @@ class ResultInline(admin.TabularInline): @admin.register(Trial) class TrialAdmin(admin.ModelAdmin): - search_fields = ['id', 'name', 'session__id'] + search_fields = ['id', 'name', 'session__id', 'session__meta__sessionName'] list_display = ( 'id', 'name', 'session', 'status', + 'has_lidar_data', 'created_at', 'updated_at', 'server', 'git_commit', 'formatted_duration', 'formatted_count', @@ -109,8 +118,23 @@ class TrialAdmin(admin.ModelAdmin): 'trashed', 'trashed_at', ) raw_id_fields = ('session',) + readonly_fields = ('has_lidar_data',) inlines = [ResultInline] + def get_queryset(self, request): + qs = super().get_queryset(request) + lidar_videos = Video.objects.filter( + trial=OuterRef('pk'), + parameters__has_lidar_data=True, + ) + return qs.annotate(_has_lidar_data=Exists(lidar_videos)) + + def has_lidar_data(self, obj): + return obj._has_lidar_data + + has_lidar_data.short_description = 'Lidar' + has_lidar_data.boolean = True + def is_meta_null(self, obj): return obj.meta is None @@ -135,13 +159,13 @@ class ResultAdmin(admin.ModelAdmin): 'id', 'trial', 'tag', 'media', 'device_id', 'created_at', 'updated_at') - search_fields = ['id', 'trial__id'] + search_fields = ['id', 'trial__id', 'trial__name'] raw_id_fields = ('trial',) @admin.register(Video) class VideoAdmin(admin.ModelAdmin): - search_fields = ['id', 'trial__id'] + search_fields = ['id', 'trial__id', 'trial__name'] list_display = ('id', 'trial', 'video', 'created_at', 'updated_at') raw_id_fields = ('trial',) diff --git a/mcserver/serializers.py b/mcserver/serializers.py index d47233d..a6034ff 100644 --- a/mcserver/serializers.py +++ b/mcserver/serializers.py @@ -16,10 +16,24 @@ TrialTags ) from rest_framework.validators import UniqueValidator -from django.db.models import Prefetch, Q +from django.db.models import Prefetch, Q, Exists, OuterRef from django.utils.translation import gettext as _ +def annotate_has_lidar_data(queryset): + lidar_videos = Video.objects.filter( + trial=OuterRef('pk'), + parameters__has_lidar_data=True, + ) + return queryset.annotate(_has_lidar_data=Exists(lidar_videos)) + + +def trial_has_lidar_data(trial): + if hasattr(trial, '_has_lidar_data'): + return trial._has_lidar_data + return trial.video_set.filter(parameters__has_lidar_data=True).exists() + + class UserSerializer(serializers.ModelSerializer): email = serializers.EmailField( required=True, @@ -147,7 +161,15 @@ class Meta: class TrialSerializer(serializers.ModelSerializer): videos = VideoSerializer(source='video_set', many=True) results = ResultSerializer(source='result_set', many=True) - + hasLidarData = serializers.SerializerMethodField() + + @staticmethod + def setup_eager_loading(queryset): + return annotate_has_lidar_data(queryset) + + def get_hasLidarData(self, trial): + return trial_has_lidar_data(trial) + class Meta: model = Trial fields = [ @@ -156,7 +178,7 @@ class Meta: 'server', 'is_docker', 'hostname', 'processed_duration', 'processed_count', 'git_commit', - 'trashed', 'trashed_at', + 'trashed', 'trashed_at', 'hasLidarData', ] @@ -173,7 +195,12 @@ class SessionSerializer(serializers.ModelSerializer): @staticmethod def setup_eager_loading(queryset): # queryset = queryset.prefetch_related("trial_set").all() - queryset = queryset.prefetch_related(Prefetch('trial_set', queryset=Trial.objects.order_by('created_at'))).all() + queryset = queryset.prefetch_related( + Prefetch( + 'trial_set', + queryset=annotate_has_lidar_data(Trial.objects.order_by('created_at')), + ) + ).all() return queryset diff --git a/mcserver/views.py b/mcserver/views.py index a68280d..a2d4629 100644 --- a/mcserver/views.py +++ b/mcserver/views.py @@ -1563,9 +1563,11 @@ def set_session_status(self, request, pk): # - if no it asks again in 5 sec # - if yes it runs processing and sends back the results class TrialViewSet(viewsets.ModelViewSet): + queryset = Trial.objects.all().order_by("created_at") serializer_class = TrialSerializer permission_classes = [IsPublic | (IsOwner | IsAdmin | IsBackend)] + @setup_eager_loading def get_queryset(self): user = self.request.user # Admins and backend users see all trials @@ -1579,17 +1581,17 @@ def get_queryset(self): # Unauthenticated users see only public trials else: return Trial.objects.filter(session__public=True).order_by("created_at") - + def get_permissions(self): custom_permission_classes = None if self.action in ['dequeue', 'get_trials_with_status']: custom_permission_classes = [IsAdmin | IsBackend] elif self.request.method in ['POST', 'PUT', 'PATCH', 'DELETE']: custom_permission_classes = [IsOwner | IsAdmin | IsBackend] - + if custom_permission_classes is not None: return [permission() for permission in custom_permission_classes] - + return super().get_permissions() @action(detail=False) From 8d23a13cdc11a4bb5b06e729c75598eb55f2b344 Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:30:11 -0600 Subject: [PATCH 2/3] add field --- mcserver/admin.py | 2 +- mcserver/migrations/0045_video_islidar.py | 18 ++++++++++++++++++ mcserver/models.py | 1 + mcserver/serializers.py | 2 +- mcserver/views.py | 3 +++ 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 mcserver/migrations/0045_video_islidar.py diff --git a/mcserver/admin.py b/mcserver/admin.py index 01f8cc4..79e5e9c 100644 --- a/mcserver/admin.py +++ b/mcserver/admin.py @@ -142,7 +142,7 @@ class ResultAdmin(admin.ModelAdmin): @admin.register(Video) class VideoAdmin(admin.ModelAdmin): search_fields = ['id', 'trial__id'] - list_display = ('id', 'trial', 'video', 'created_at', 'updated_at') + list_display = ('id', 'trial', 'video', 'isLidar', 'created_at', 'updated_at') raw_id_fields = ('trial',) diff --git a/mcserver/migrations/0045_video_islidar.py b/mcserver/migrations/0045_video_islidar.py new file mode 100644 index 0000000..512deab --- /dev/null +++ b/mcserver/migrations/0045_video_islidar.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2026-06-13 06:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mcserver', '0044_auto_20250606_1629'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='isLidar', + field=models.BooleanField(default=False), + ), + ] diff --git a/mcserver/models.py b/mcserver/models.py index 5ce5d9e..101af3c 100644 --- a/mcserver/models.py +++ b/mcserver/models.py @@ -178,6 +178,7 @@ class Video(models.Model): video_thumb = models.FileField(blank=True, null=True, upload_to=random_filename) keypoints = models.FileField(blank=True, null=True) parameters = models.JSONField(blank=True, null=True) + isLidar = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True, db_index=True) updated_at = models.DateTimeField(auto_now=True, db_index=True) diff --git a/mcserver/serializers.py b/mcserver/serializers.py index d47233d..0772fb7 100644 --- a/mcserver/serializers.py +++ b/mcserver/serializers.py @@ -131,7 +131,7 @@ class VideoSerializer(serializers.ModelSerializer): required=False) class Meta: model = Video - fields = ['id', 'trial', 'device_id', 'video', 'video_url', 'video_thumb', 'parameters', 'created_at', 'updated_at'] + fields = ['id', 'trial', 'device_id', 'video', 'video_url', 'video_thumb', 'parameters', 'isLidar', 'created_at', 'updated_at'] # Serializers define the API representation. diff --git a/mcserver/views.py b/mcserver/views.py index a68280d..8f2d95f 100644 --- a/mcserver/views.py +++ b/mcserver/views.py @@ -1879,6 +1879,9 @@ def perform_update(self, serializer): serializer.validated_data["video"] = serializer.validated_data["video_url"] del serializer.validated_data["video_url"] + if "isLidar" in self.request.data: + serializer.validated_data["isLidar"] = str(self.request.data.get("isLidar", "")).lower() == "true" + super().perform_update(serializer) class ResultViewSet(viewsets.ModelViewSet): From fb43fb5e831f9b0ebd2c07daa877913fd7bac43c Mon Sep 17 00:00:00 2001 From: suhlrich <43877159+suhlrich@users.noreply.github.com> Date: Sat, 13 Jun 2026 01:40:38 -0600 Subject: [PATCH 3/3] change how lidar is denoted --- mcserver/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcserver/admin.py b/mcserver/admin.py index 094b0b1..79d48ca 100644 --- a/mcserver/admin.py +++ b/mcserver/admin.py @@ -125,7 +125,7 @@ def get_queryset(self, request): qs = super().get_queryset(request) lidar_videos = Video.objects.filter( trial=OuterRef('pk'), - parameters__has_lidar_data=True, + isLidar=True, ) return qs.annotate(_has_lidar_data=Exists(lidar_videos))