From 5055642fdb8f1ca369595169bdf4d06f14953a20 Mon Sep 17 00:00:00 2001 From: dglemos Date: Tue, 27 Jan 2026 16:22:03 +0000 Subject: [PATCH] Validate curation draft before update --- .../serializers/curation.py | 16 ++-- .../tests/curation/test_update_curation.py | 91 ++++++++++++++++++- .../gene2phenotype_app/views/curation.py | 4 + 3 files changed, 100 insertions(+), 11 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/curation.py b/gene2phenotype_project/gene2phenotype_app/serializers/curation.py index 04b85ace..6a3b19e3 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/curation.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/curation.py @@ -45,17 +45,16 @@ class CurationDataSerializer(serializers.ModelSerializer): def validate_to_save(self, data): """ - Validate the input data for curation. + Validate the input data to add or save a curation draft. - Validation extension step: - This step is called in AddCurationData of the views.py - The steps of the validation for the save is - - Locus is the minimum requirement needed to save a draft - - Draft does not already exist as a draft - - User has permissions to curate on the selected panel + Validation steps: + - Locus is the minimum requirement needed to save a draft + - Draft does not already exist as a draft + - User has permissions to curate on the selected panels Args: - data: The data to be validated. + data: The data to be validated. Input format: + {'json_data': {...}, 'status': 'manual'} OR {'json_data': {...}} Returns: The validated data. @@ -290,6 +289,7 @@ def compare_curation_data(self, input_json_data, user_obj): # remove session_name field from input json and compare input json with existing curation json input_json_data["json_data"].pop("session_name", None) data_json.pop("session_name", None) + result = DeepDiff(input_json_data["json_data"], data_json) if not result: diff --git a/gene2phenotype_project/gene2phenotype_app/tests/curation/test_update_curation.py b/gene2phenotype_project/gene2phenotype_app/tests/curation/test_update_curation.py index ad4ea7af..e87562d5 100644 --- a/gene2phenotype_project/gene2phenotype_app/tests/curation/test_update_curation.py +++ b/gene2phenotype_project/gene2phenotype_app/tests/curation/test_update_curation.py @@ -24,7 +24,9 @@ def setUp(self): self.url_update_curation = reverse( "update_curation", kwargs={"stable_id": "G2P00004"} ) - + self.url_update_curation_2 = reverse( + "update_curation", kwargs={"stable_id": "G2P00010"} + ) self.url_update_invalid_curation = reverse( "update_curation", kwargs={"stable_id": "G2P00123"} ) @@ -327,7 +329,6 @@ def test_update_curation_empty_locus(self): """ self.login_user() - # Define the complex data structure curation_to_update = { "json_data": { "allelic_requirement": "", @@ -358,7 +359,45 @@ def test_update_curation_empty_locus(self): self.assertEqual(response.status_code, 400) response_data = response.json() - self.assertEqual(response_data["error"],"Invalid gene ''") + self.assertEqual(response_data["error"], "To save a draft, the minimum requirement is a locus entry. Please save this draft with locus information") + + def test_update_curation_invalid_locus(self): + """ + Test call to update curation endpoint with invalid locus field + """ + self.login_user() + + curation_to_update = { + "json_data": { + "allelic_requirement": "", + "confidence": "", + "cross_cutting_modifier": [], + "disease": {"cross_references": [], "disease_name": ""}, + "locus": "BAAT-21", + "mechanism_evidence": [], + "mechanism_synopsis": [], + "molecular_mechanism": {"name": "", "support": ""}, + "panels": [], + "phenotypes": [], + "private_comment": "", + "public_comment": "", + "publications": [], + "session_name": "test session", + "variant_consequences": [], + "variant_descriptions": [], + "variant_types": [], + } + } + + response = self.client.put( + self.url_update_curation, + curation_to_update, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + + response_data = response.json() + self.assertEqual(response_data["error"], "Invalid gene 'BAAT-21'") def test_update_invalid_curation(self): """ @@ -396,3 +435,49 @@ def test_update_invalid_curation(self): content_type="application/json", ) self.assertEqual(response.status_code, 404) + + def test_update_curation_duplicate(self): + """ + Test to update curation that would create a duplicate entry + G2P ID G2P00010 has session name 'test session SRY', this test + tries to update the json content to be the same as session 'test session' (G2P00004) + """ + self.login_user() + + curation_to_update = { + "json_data": { + "allelic_requirement": "", + "confidence": "", + "cross_cutting_modifier": [], + "disease": { + "cross_references": [], + "disease_name": "" + }, + "locus": "CEP290", + "mechanism_evidence": [], + "mechanism_synopsis": [], + "molecular_mechanism": { + "name": "", + "support": "" + }, + "panels": [], + "phenotypes": [], + "private_comment": "", + "public_comment": "", + "publications": [], + "session_name": "test session SRY", + "variant_consequences": [], + "variant_descriptions": [], + "variant_types": [] + } + } + + response = self.client.put( + self.url_update_curation_2, + curation_to_update, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + + response_data = response.json() + self.assertEqual(response_data["error"], "Data already under curation. Please check session 'test session'") diff --git a/gene2phenotype_project/gene2phenotype_app/views/curation.py b/gene2phenotype_project/gene2phenotype_app/views/curation.py index 28fca646..8d7caae7 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/curation.py +++ b/gene2phenotype_project/gene2phenotype_app/views/curation.py @@ -287,6 +287,10 @@ def update(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) + # Check if input data (json) is already present for this user + # Do not use 'input_data' because it is a result of json.dumps() + self.serializer_class(context={"user": user}).validate_to_save(request.data) + # Update data - it replaces the data serializer = CurationDataSerializer( curation_obj,