diff --git a/mcserver/admin.py b/mcserver/admin.py index 01f8cc4..79d48ca 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'), + isLidar=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,14 +159,15 @@ 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'] - list_display = ('id', 'trial', 'video', 'created_at', 'updated_at') + search_fields = ['id', 'trial__id', 'trial__name'] + 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..7d54c66 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, @@ -131,7 +145,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. @@ -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..183bec2 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) @@ -1879,6 +1881,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):