Repository: ResonantGeoData/ResonantGeoData

Issue:
SubsampledImage: prevent duplicate sample_parameters
We should do a check to see if the `sample_parameters` for any created `SubsampledImage` object match any existing entry to prevent duplication of data.


Discussion:

Resolving patch:
diff --git a/rgd/geodata/admin.py b/rgd/geodata/admin.py
--- a/rgd/geodata/admin.py
+++ b/rgd/geodata/admin.py
@@ -27,6 +27,7 @@
 SPATIAL_ENTRY_FILTERS = (
     'acquisition_date',
     'modified',
+    'created',
 )
 
 TASK_EVENT_READONLY = (
@@ -40,6 +41,8 @@ class ArbitraryFileAdmin(OSMGeoAdmin):
     list_display = (
         'id',
         'name',
+        'modified',
+        'created',
     )
     readonly_fields = ('checksum',)
 
@@ -50,6 +53,8 @@ class KWCOCOArchiveAdmin(OSMGeoAdmin):
         'id',
         'name',
         'status',
+        'modified',
+        'created',
     )
     readonly_fields = ('image_set',) + TASK_EVENT_READONLY
 
@@ -60,6 +65,8 @@ class ImageSetAdmin(OSMGeoAdmin):
         'id',
         'name',
         'count',
+        'modified',
+        'created',
     )
     actions = (actions.make_raster_from_image_set,)
 
@@ -87,6 +94,7 @@ class BandMetaEntryInline(admin.StackedInline):
         'id',
         'parent_image',
         'modified',
+        'created',
     )
     readonly_fields = (
         'mean',
@@ -119,6 +127,7 @@ class ImageEntryAdmin(OSMGeoAdmin):
         'name',
         'image_file',
         'modified',
+        'created',
     )
     readonly_fields = (
         'number_of_bands',
@@ -145,6 +154,7 @@ class RasterMetaEntryInline(admin.StackedInline):
     list_display = (
         'id',
         'modified',
+        'created',
     )
     readonly_fields = (
         'crs',
@@ -168,6 +178,7 @@ class RasterEntryAdmin(OSMGeoAdmin):
         'name',
         'status',
         'modified',
+        'created',
     )
     readonly_fields = (
         'modified',
@@ -209,6 +220,8 @@ class AnnotationAdmin(OSMGeoAdmin):
         'label',
         'annotator',
         'segmentation_type',
+        'modified',
+        'created',
     )
     readonly_fields = (
         'keypoints',
@@ -224,6 +237,7 @@ class ImageFileAdmin(OSMGeoAdmin):
         'name',
         'status',
         'modified',
+        'created',
         'image_data_link',
     )
     readonly_fields = ('modified', 'created', 'checksum', 'last_validation') + TASK_EVENT_READONLY
@@ -237,6 +251,7 @@ class ConvertedImageFileAdmin(OSMGeoAdmin):
         'source_image',
         'status',
         'modified',
+        'created',
     )
     readonly_fields = ('converted_file',) + TASK_EVENT_READONLY
 
@@ -249,6 +264,7 @@ class SubsampledImageAdmin(OSMGeoAdmin):
         'sample_type',
         'status',
         'modified',
+        'created',
     )
     readonly_fields = ('data',) + TASK_EVENT_READONLY
 
@@ -261,6 +277,7 @@ class GeometryEntryInline(admin.StackedInline):
         'name',
         'geometry_archive',
         'modified',
+        'created',
     )
     list_filter = SPATIAL_ENTRY_FILTERS
     readonly_fields = (
@@ -278,6 +295,7 @@ class GeometryArchiveAdmin(OSMGeoAdmin):
         'name',
         'status',
         'modified',
+        'created',
         'archive_data_link',
     )
     readonly_fields = (
@@ -292,7 +310,13 @@ class GeometryArchiveAdmin(OSMGeoAdmin):
 class FMVEntryInline(admin.StackedInline):
     model = FMVEntry
     fk_name = 'fmv_file'
-    list_display = ('id', 'name', 'fmv_file')
+    list_display = (
+        'id',
+        'name',
+        'fmv_file',
+        'modified',
+        'created',
+    )
     readonly_fields = (
         'modified',
         'created',
@@ -306,6 +330,7 @@ class FMVFileAdmin(OSMGeoAdmin):
         'name',
         'status',
         'modified',
+        'created',
         'fmv_data_link',
         'klv_data_link',
     )
diff --git a/rgd/geodata/serializers.py b/rgd/geodata/serializers.py
--- a/rgd/geodata/serializers.py
+++ b/rgd/geodata/serializers.py
@@ -83,5 +83,26 @@ class Meta:
             'data',
         ]
 
+    def create(self, validated_data):
+        """Prevent duplicated subsamples from being created.
+
+        This will check to see if an existing model matches the
+        ``sample_parameters`` for the given image ID and return that object
+        instead of creating another.
+
+        """
+        sample_parameters = validated_data['sample_parameters']
+        source_image = validated_data['source_image']
+        q = models.SubsampledImage.objects.filter(
+            sample_parameters=sample_parameters, source_image=source_image
+        )
+        if q.count() == 0:
+            obj = models.SubsampledImage.objects.create(**validated_data)
+        else:
+            obj = q.first()
+            # Trigger a reprocessing
+            obj.save()
+        return obj
+
 
 utility.make_serializers(globals(), models)


Test patch:
diff --git a/rgd/geodata/tests/test_api.py b/rgd/geodata/tests/test_api.py
--- a/rgd/geodata/tests/test_api.py
+++ b/rgd/geodata/tests/test_api.py
@@ -115,14 +115,12 @@ def test_get_spatial_entry(client, landsat_raster):
 @pytest.mark.django_db(transaction=True)
 def test_create_get_subsampled_image(client, astro_image):
     """Test POST and GET for SubsampledImage model."""
-    response = client.post(
-        '/api/geodata/imagery/subsample',
-        {
-            'source_image': astro_image.pk,
-            'sample_type': 'pixel box',
-            'sample_parameters': json.dumps({'umax': 100, 'umin': 0, 'vmax': 200, 'vmin': 0}),
-        },
-    )
+    payload = {
+        'source_image': astro_image.pk,
+        'sample_type': 'pixel box',
+        'sample_parameters': json.dumps({'umax': 100, 'umin': 0, 'vmax': 200, 'vmin': 0}),
+    }
+    response = client.post('/api/geodata/imagery/subsample', payload)
     assert response.status_code == 201, response.content
     content = json.loads(response.content)
     pk = content['pk']
@@ -131,6 +129,11 @@ def test_create_get_subsampled_image(client, astro_image):
     # Test the GET
     response = client.get(f'/api/geodata/imagery/subsample/{pk}')
     assert response.status_code == 200, response.content
+    # Now test to make sure the serializer prevents duplicates
+    response = client.post('/api/geodata/imagery/subsample', payload)
+    assert response.status_code == 201, response.content
+    content = json.loads(response.content)
+    assert pk == content['pk']  # Compare against original PK
 
 
 @pytest.mark.django_db(transaction=True)