Repository: comic/grand-challenge.org

Issue:
Partial update not working correctly because of custom validation
The validation rule in `app/grandchallenge/annotations/serializers.py:31` is breaking the partial update functionality.
If you try to do a partial update PATCH request to the endpoint, it will try to find the `annotation_set` attribute in the request data. If this is not present it will throw a KeyError. 

This should be fixed by first checking if the key exists in the request data and only then running the validation check. The validation check is not needed if the key does not exist because it will then either not change (for partial update request) or throw a `field is required` validation error (for every other type of request).

I will fix this and add a test for it.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/annotations/serializers.py b/app/grandchallenge/annotations/serializers.py
--- a/app/grandchallenge/annotations/serializers.py
+++ b/app/grandchallenge/annotations/serializers.py
@@ -27,11 +27,14 @@ class Meta:
 class AbstractSingleAnnotationSerializer(serializers.ModelSerializer):
     def validate(self, data):
         """
-        Validate that the user that is creating this object equals the annotation_set.grader for retina_graders
+        Validate that the user that is creating this object equals the
+        annotation_set.grader for retina_graders
         """
-        validate_grader_is_current_retina_user(
-            data["annotation_set"].grader, self.context
-        )
+        if data.get("annotation_set") is None:
+            return data
+
+        grader = data["annotation_set"].grader
+        validate_grader_is_current_retina_user(grader, self.context)
         return data
 
     class Meta:


Test patch:
diff --git a/app/tests/retina_api_tests/test_viewsets.py b/app/tests/retina_api_tests/test_viewsets.py
--- a/app/tests/retina_api_tests/test_viewsets.py
+++ b/app/tests/retina_api_tests/test_viewsets.py
@@ -1,3 +1,4 @@
+import copy
 import json
 import pytest
 
@@ -522,11 +523,12 @@ def test_partial_update_view(
         model_serialized = SinglePolygonAnnotationSerializer(
             TwoRetinaPolygonAnnotationSets.polygonset1.singlepolygonannotation_set.first()
         ).data
-        annotation_set = PolygonAnnotationSetFactory(
-            grader=TwoRetinaPolygonAnnotationSets.grader1
-        )
-        model_serialized["annotation_set"] = str(annotation_set.id)
-        model_json = json.dumps(model_serialized)
+        annotation_set = SinglePolygonAnnotationFactory()
+        model_serialized["value"] = annotation_set.value
+        partial_model = copy.deepcopy(model_serialized)
+        del partial_model["annotation_set"]
+        del partial_model["id"]
+        model_json = json.dumps(partial_model)
 
         response = view_test(
             "partial_update",
@@ -541,9 +543,6 @@ def test_partial_update_view(
         )
 
         if user_type in ("retina_grader", "retina_admin"):
-            response.data["annotation_set"] = str(
-                response.data["annotation_set"]
-            )
             assert response.data == model_serialized
 
     def test_destroy_view(self, TwoRetinaPolygonAnnotationSets, rf, user_type):