Repository: ResonantGeoData/ResonantGeoData

Issue:
500 error on FMV Detail View page
I noticed this bug when trying to use the FMV module on Danesfield. I tested it on a fresh RGD instance and it still occured.

To reproduce -
- Run the `rgd_fmv_wasabi` management command to populate your DB with FMV data
- Attempt to navigate to `http://localhost:8000/rgd_fmv/<:id>/` and it will return a 500 error with the error `string indices must be integers
`.
500 error on `/rgd_fmv/{spatial_id}` endpoint
I noticed this bug when trying to use the FMV module on Danesfield. I tested it on a fresh RGD instance and it still occurred.

To reproduce -

- Run the `rgd_fmv_wasabi` management command to populate your DB with FMV data
- Hit the `/rgd_fmv/{spatial_id}` API endpoint and a 500 will be returned with an error ```AttributeError at /api/rgd_fmv/1
Got AttributeError when attempting to get a value for field `file` on serializer `FMVSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `FMVMeta` instance.
Original exception text was: 'FMVMeta' object has no attribute 'file'.```


Discussion:

Resolving patch:
diff --git a/django-rgd-fmv/rgd_fmv/rest/viewsets.py b/django-rgd-fmv/rgd_fmv/rest/viewsets.py
--- a/django-rgd-fmv/rgd_fmv/rest/viewsets.py
+++ b/django-rgd-fmv/rgd_fmv/rest/viewsets.py
@@ -1,16 +1,17 @@
 from rest_framework.decorators import action
-from rgd.rest.base import ModelViewSet
+from rgd.rest.base import ModelViewSet, ReadOnlyModelViewSet
 from rgd_fmv import models, serializers
 
 
-class FMVViewSet(ModelViewSet):
+class FMVMetaViewSet(ReadOnlyModelViewSet):
     queryset = models.FMVMeta.objects.all()
-
-    def get_serializer_class(self):
-        if self.action in ['get', 'list']:
-            return serializers.FMVMetaSerializer
-        return serializers.FMVSerializer
+    serializer_class = serializers.FMVMetaSerializer
 
     @action(detail=True, serializer_class=serializers.FMVMetaDataSerializer)
     def data(self, request, *args, **kwargs):
         return self.retrieve(request, *args, **kwargs)
+
+
+class FMVViewSet(ModelViewSet):
+    queryset = models.FMV.objects.all()
+    serializer_class = serializers.FMVSerializer
diff --git a/django-rgd-fmv/rgd_fmv/urls.py b/django-rgd-fmv/rgd_fmv/urls.py
--- a/django-rgd-fmv/rgd_fmv/urls.py
+++ b/django-rgd-fmv/rgd_fmv/urls.py
@@ -4,7 +4,8 @@
 from rgd_fmv.rest import viewsets
 
 router = SimpleRouter(trailing_slash=False)
-router.register(r'api/rgd_fmv', viewsets.FMVViewSet)
+router.register(r'api/rgd_fmv', viewsets.FMVMetaViewSet, basename='fmv-meta')
+router.register(r'api/rgd_fmv/model', viewsets.FMVViewSet, basename='fmv')
 
 urlpatterns = [
     # Pages
diff --git a/django-rgd-fmv/rgd_fmv/views.py b/django-rgd-fmv/rgd_fmv/views.py
--- a/django-rgd-fmv/rgd_fmv/views.py
+++ b/django-rgd-fmv/rgd_fmv/views.py
@@ -11,10 +11,11 @@ class FMVMetaDetailView(SpatialDetailView):
     def get_context_data(self, *args, **kwargs):
         context = super().get_context_data(*args, **kwargs)
         context['frame_rate'] = json.dumps(self.object.fmv_file.frame_rate)
-        extents = context['extents']
+        extents = json.loads(context['extents'])
         if self.object.ground_union is not None:
             # All or none of these will be set, only check one
             extents['collect'] = self.object.ground_union.json
             extents['ground_frames'] = self.object.ground_frames.json
             extents['frame_numbers'] = self.object._blob_to_array(self.object.frame_numbers)
+        context['extents'] = json.dumps(extents)
         return context


Test patch:
diff --git a/django-rgd-fmv/tests/test_rest.py b/django-rgd-fmv/tests/test_rest.py
--- a/django-rgd-fmv/tests/test_rest.py
+++ b/django-rgd-fmv/tests/test_rest.py
@@ -1,8 +1,17 @@
 import pytest
 from rest_framework import status
+from rest_framework.test import APIClient
+from rgd_fmv.models import FMV
+from rgd_fmv.serializers import FMVMetaSerializer
 
 
 @pytest.mark.django_db(transaction=True)
 def test_swagger(admin_api_client):
     response = admin_api_client.get('/swagger/?format=openapi')
     assert status.is_success(response.status_code)
+
+
+@pytest.mark.django_db(transaction=True)
+def test_fmv_spatial_entry(admin_api_client: APIClient, fmv_klv_file: FMV):
+    resp = admin_api_client.get(f'/rgd_fmv_test/api/rgd_fmv/{fmv_klv_file.fmvmeta.pk}')
+    assert resp.json() == FMVMetaSerializer(fmv_klv_file.fmvmeta).data
diff --git a/django-rgd/tests/test_client_plugins.py b/django-rgd/tests/test_client_plugins.py
--- a/django-rgd/tests/test_client_plugins.py
+++ b/django-rgd/tests/test_client_plugins.py
@@ -1,3 +1,5 @@
+from django.contrib.auth.models import User
+from rest_framework.authtoken.models import Token
 from rgd_client import create_rgd_client
 from rgd_client.plugin import CorePlugin, RgdPlugin
 
@@ -25,8 +27,22 @@ class MyClient(ClientA, ClientB):
     pass
 
 
-def test_plugin_dependency_inject():
-    client: MyClient = create_rgd_client(extra_plugins=[ClientA, ClientB])
+def test_plugin_dependency_inject(live_server):
+
+    params = {'username': 'test@kitware.com', 'email': 'test@kitware.com', 'password': 'password'}
+
+    user = User.objects.create_user(is_staff=True, is_superuser=True, **params)
+    user.save()
+
+    # use constant value for API key so this client fixture can be reused across multiple tests
+    Token.objects.create(user=user, key='topsecretkey')
+
+    client = create_rgd_client(
+        username=params['username'],
+        password=params['password'],
+        api_url=f'{live_server.url}/api',
+        extra_plugins=[ClientA, ClientB],
+    )
 
     # Assert plugins were loaded correctly
     assert isinstance(client.a.rgd, CorePlugin)