From 886cc7fb0be297099b8807db967f58ef1a6bcd66 Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 23 Oct 2024 16:08:07 +0100 Subject: [PATCH 01/20] Fix bug in endpoint to add lgd-publication --- .../serializers/publication.py | 45 ++++++++-------- .../gene2phenotype_app/views/publication.py | 51 ++++++++++++++++--- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py index cfbf616b..302c6835 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py @@ -302,18 +302,21 @@ def validate(self, data): if hasattr(self, 'initial_data'): data = self.initial_data valid_headers = ["families", "consanguinity", "ancestries", "affected_individuals"] + extra_headers = ["phenotypes", "variant_types", "variant_descriptions"] publication = self.initial_data.get("publication") # check if 'families' is defined # correct structure is: # { "families": 200, "consanguinity": "unknown", "ancestries": "african", # "affected_individuals": 100 } - if "families" in publication and publication.get("families") is not None: - families = publication.get("families") + if "families" in publication: + families = publication.get("families", None) for header in families.keys(): if header not in valid_headers: raise serializers.ValidationError(f"Got unknown field in families: {header}") + print("Validate LGDPublicationSerializer") + return data def create(self, validated_data): @@ -335,24 +338,21 @@ def create(self, validated_data): comment = None families = None - publication_data = validated_data.get('publication') # includes 'pmid', 'comment', 'families' + publication_data = self.initial_data.get('publication') # 'publication': {'pmid': 39385417} + comment = self.initial_data.get('comment', None) # 'comment': {'comment': 'this is a comment', 'is_public': 1} + families = self.initial_data.get("families", None) # "families": { "families": 200, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 2 } + + print("->", self.initial_data) - if "comment" in publication_data: - comment = publication_data.get("comment") + if comment: + comment_text = comment.get("comment", None) - # Check if comment text is invalid or missing - if not comment or "comment" not in comment or comment.get("comment") == "": + # Check if comment text is empty string + if not comment_text or comment_text == "": comment = None # If 'is_public' is not defined set it to public (default) - elif "is_public" not in comment: - comment["is_public"] = 1 - - if "families" in publication_data: - families = publication_data.get('families') # extra data - families reported in publication - - # Check if family data is invalid or missing - if not families: - families = None + else: + comment["is_public"] = comment.get("is_public", 1) # it is necessary to send the user # the publication comment is linked to the user @@ -387,6 +387,10 @@ def create(self, validated_data): lgd_publication_obj.is_deleted = 0 lgd_publication_obj.save() + # When we add a publication to a LGD record, we should also link the new publication to + # other existing/new data (LGDPhenotype, LGDPhenotypeSummary, LGDVariantType, LGDVariantTypeDescription) + + return lgd_publication_obj class Meta: @@ -396,6 +400,7 @@ class Meta: class LGDPublicationListSerializer(serializers.Serializer): """ Serializer to accept a list of publications. + This method only validates the publications, it does not update any data. Called by: LocusGenotypeDiseaseAddPublication() """ publications = LGDPublicationSerializer(many=True) @@ -411,7 +416,8 @@ def validate(self, data): # by default these fields are not accepted as valid as they are not part of the LGDPublication if hasattr(self, 'initial_data'): data = self.initial_data - valid_headers = ["families", "consanguinity", "ancestries", "affected_individuals"] + families_valid_headers = ["families", "consanguinity", "ancestries", "affected_individuals"] + extra_headers = ["phenotypes", "variant_types", "variant_descriptions"] publications = self.initial_data.get("publications") @@ -420,12 +426,11 @@ def validate(self, data): # check if 'families' is defined # correct structure is: - # { "families": 200, "consanguinity": "unknown", "ancestries": "african", - # "affected_individuals": 100 } + # "families": { "families": 2, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 1 } if "families" in publication: families = publication.get("families") for header in families.keys(): - if header not in valid_headers: + if header not in families_valid_headers: raise serializers.ValidationError(f"Got unknown field in families: {header}") # TODO check if 'comment' is defined diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 1021a475..a7e793e8 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -146,16 +146,44 @@ def post(self, request, stable_id): The post method creates an association between the current LGD record and a list of publications. We want to whole process to be done in one db transaction. + This method allows to add extra data to the LGD record. + When a publication is linked to a LGD record, other types of data can be associated to the record + and the publication: + - phenotypes + - variant types + - variant descriptions + Args: (dict) request - + Example: { "publications":[ - { - "publication": { "pmid": 1234 }, - "comment": { "comment": "this is a comment", "is_public": 1 }, - "families": { "families": 2, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 1 } - } + { + "publication": { "pmid": 1234 }, + "comment": { "comment": "this is a comment", "is_public": 1 }, + "families": { "families": 2, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 1 }, + "phenotypes": [{ + "pmid": "41", + "summary": "", + "hpo_terms": [{ "term": "Orofacial dyskinesia", + "accession": "HP:0002310", + "description": "" }] + }], + "variant_types": [{ + "comment": "", + "de_novo": false, + "inherited": false, + "nmd_escape": false, + "primary_type": "protein_changing", + "secondary_type": "inframe_insertion", + "supporting_papers": ["41"], + "unknown_inheritance": true + }], + "variant_descriptions": [{ + "description": "HGVS:c.9Pro", + "publication": "41" + }] + } ] } """ @@ -177,6 +205,17 @@ def post(self, request, stable_id): if serializer_class.is_valid(): serializer_class.save() + + # Add extra data linked to the publication + if "phenotypes" in publication: + print("Add phenotypes!!") + + if "variant_types" in publication: + print("Add variant_types!!") + + if "variant_descriptions" in publication: + print("Add variant_descriptions!!") + response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) else: response = Response({"errors": serializer_class.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) From 1c93e9637379e2b371489c9f37998fd94b8f3c2a Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 30 Oct 2024 11:56:00 +0000 Subject: [PATCH 02/20] Populate lgd-phenotypes --- .../serializers/phenotype.py | 2 +- .../serializers/publication.py | 17 ++---- .../gene2phenotype_app/views/publication.py | 58 +++++++++++++------ 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/phenotype.py b/gene2phenotype_project/gene2phenotype_app/serializers/phenotype.py index 75b31fda..49b1fabe 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/phenotype.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/phenotype.py @@ -131,7 +131,7 @@ class Meta: class LGDPhenotypeListSerializer(serializers.Serializer): """ Serializer to accept a list of phenotypes. - Called by: LocusGenotypeDiseaseAddPhenotypes() + Called by: LocusGenotypeDiseaseAddPhenotypes() and view LGDEditPhenotypes() """ phenotypes = LGDPhenotypeSerializer(many=True) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py index 302c6835..e1ce9438 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py @@ -296,27 +296,24 @@ def validate(self, data): """ Overwrite the method to validate the data. This validate() method is activated by the curation (draft entry) endpoints and - by the LGDPublicationListSerializer as a consequence this method is identical to - the validate method in LGDPublicationListSerializer. + by the LGDPublicationListSerializer. + This method is identical to the validate method in LGDPublicationListSerializer. """ if hasattr(self, 'initial_data'): data = self.initial_data valid_headers = ["families", "consanguinity", "ancestries", "affected_individuals"] extra_headers = ["phenotypes", "variant_types", "variant_descriptions"] - publication = self.initial_data.get("publication") - # check if 'families' is defined + # check if 'families' is defined in the initial data # correct structure is: # { "families": 200, "consanguinity": "unknown", "ancestries": "african", # "affected_individuals": 100 } - if "families" in publication: - families = publication.get("families", None) + if "families" in self.initial_data: + families = self.initial_data.get("families") # If 'families' is in initial data then it cannot be null for header in families.keys(): if header not in valid_headers: raise serializers.ValidationError(f"Got unknown field in families: {header}") - print("Validate LGDPublicationSerializer") - return data def create(self, validated_data): @@ -342,8 +339,6 @@ def create(self, validated_data): comment = self.initial_data.get('comment', None) # 'comment': {'comment': 'this is a comment', 'is_public': 1} families = self.initial_data.get("families", None) # "families": { "families": 200, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 2 } - print("->", self.initial_data) - if comment: comment_text = comment.get("comment", None) @@ -401,7 +396,7 @@ class LGDPublicationListSerializer(serializers.Serializer): """ Serializer to accept a list of publications. This method only validates the publications, it does not update any data. - Called by: LocusGenotypeDiseaseAddPublication() + Called by: LocusGenotypeDiseaseAddPublication() and view LGDEditPublications() """ publications = LGDPublicationSerializer(many=True) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index a7e793e8..4a4339d7 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -6,14 +6,13 @@ from django.shortcuts import get_object_or_404 from gene2phenotype_app.serializers import (PublicationSerializer, LGDPublicationSerializer, - LGDPublicationListSerializer) + LGDPublicationListSerializer, LGDPhenotypeSerializer) from gene2phenotype_app.models import (Publication, LocusGenotypeDisease, LGDPublication, LGDPhenotype, LGDPhenotypeSummary, LGDVariantType, - LGDVariantTypeDescription, MolecularMechanism, - MolecularMechanismEvidence) + LGDVariantTypeDescription, MolecularMechanismEvidence) -from .base import BaseAdd, BaseUpdate +from .base import BaseAdd from ..utils import get_publication, get_authors @@ -154,7 +153,7 @@ def post(self, request, stable_id): - variant descriptions Args: - (dict) request + (dict) request data Example: { "publications":[ @@ -203,23 +202,48 @@ def post(self, request, stable_id): context={"lgd": lgd, "user": user} ) + # Insert new publication if serializer_class.is_valid(): serializer_class.save() - - # Add extra data linked to the publication - if "phenotypes" in publication: - print("Add phenotypes!!") - - if "variant_types" in publication: - print("Add variant_types!!") - - if "variant_descriptions" in publication: - print("Add variant_descriptions!!") - - response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) else: response = Response({"errors": serializer_class.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + # Add extra data linked to the publication + if "phenotypes" in publication: + # Expected structure: + # { "phenotypes": [{ "accession": "HP:0003974", "publication": 1 }] } + for pheno in publication.get("phenotypes"): + hpo_terms = pheno['hpo_terms'] + for hpo in hpo_terms: + phenotype_data = { + "accession": hpo["accession"], + "publication": pheno["pmid"] + } + try: + lgd_phenotype_serializer = LGDPhenotypeSerializer( + data = phenotype_data, + context = {'lgd': lgd} + ) + # Validate the input data + if lgd_phenotype_serializer.is_valid(): + # save() is going to call create() + lgd_phenotype_serializer.save() + else: + response = Response({"errors": lgd_phenotype_serializer.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + except: + return Response( + {"errors": f"Could not insert phenotype '{phenotype_data["accession"]}' for ID '{stable_id}'"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + + if "variant_types" in publication: + print("Add variant_types!!") + + if "variant_descriptions" in publication: + print("Add variant_descriptions!!") + + response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) + else: response = Response({"errors": serializer_list.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) From 26e8ec33c060d576edb2a9bfd529a6cb0c4be70c Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 30 Oct 2024 14:53:26 +0000 Subject: [PATCH 03/20] Add variant info --- .../gene2phenotype_app/views/publication.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 4a4339d7..56771d74 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -6,7 +6,8 @@ from django.shortcuts import get_object_or_404 from gene2phenotype_app.serializers import (PublicationSerializer, LGDPublicationSerializer, - LGDPublicationListSerializer, LGDPhenotypeSerializer) + LGDPublicationListSerializer, LGDPhenotypeSerializer, + LGDVariantTypeSerializer, LGDVariantTypeDescriptionSerializer) from gene2phenotype_app.models import (Publication, LocusGenotypeDisease, LGDPublication, LGDPhenotype, LGDPhenotypeSummary, LGDVariantType, @@ -212,7 +213,7 @@ def post(self, request, stable_id): if "phenotypes" in publication: # Expected structure: # { "phenotypes": [{ "accession": "HP:0003974", "publication": 1 }] } - for pheno in publication.get("phenotypes"): + for pheno in publication["phenotypes"]: hpo_terms = pheno['hpo_terms'] for hpo in hpo_terms: phenotype_data = { @@ -231,16 +232,19 @@ def post(self, request, stable_id): else: response = Response({"errors": lgd_phenotype_serializer.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except: + accession = phenotype_data["accession"] return Response( - {"errors": f"Could not insert phenotype '{phenotype_data["accession"]}' for ID '{stable_id}'"}, + {"errors": f"Could not insert phenotype '{accession}' for ID '{stable_id}'"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) if "variant_types" in publication: - print("Add variant_types!!") - + for variant_type in publication["variant_types"]: + LGDVariantTypeSerializer(context={'lgd': lgd, 'user': user}).create(variant_type) + if "variant_descriptions" in publication: - print("Add variant_descriptions!!") + for variant_type_desc in publication["variant_descriptions"]: + LGDVariantTypeDescriptionSerializer(context={'lgd': lgd}).create(variant_type_desc) response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) From 32316921a9b6060ab64d7283349302ce7a5c0b2d Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 30 Oct 2024 15:13:59 +0000 Subject: [PATCH 04/20] Add mechanism evidence --- .../gene2phenotype_app/views/publication.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 56771d74..6424f7ee 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -152,11 +152,12 @@ def post(self, request, stable_id): - phenotypes - variant types - variant descriptions + - molecular mechanism evidence Args: (dict) request data - Example: + Example for a record already linked to pmid '41': { "publications":[ { "publication": { "pmid": 1234 }, @@ -183,9 +184,13 @@ def post(self, request, stable_id): "description": "HGVS:c.9Pro", "publication": "41" }] - } - ] - } + "mechanism_evidence": [{ + "pmid": "1234", + "description": "This is new evidence for the existing mechanism evidence.", + "evidence_types": [ { "primary_type": "Function", + "secondary_type": [ "Biochemical" ]} + ]}] + }]} """ user = self.request.user @@ -246,6 +251,9 @@ def post(self, request, stable_id): for variant_type_desc in publication["variant_descriptions"]: LGDVariantTypeDescriptionSerializer(context={'lgd': lgd}).create(variant_type_desc) + if "mechanism_evidence" in publication: + print("Add mechanism evidence!!") + response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) else: From 2bbaa24d163d9fdf32812faeb3d0ca1b5929d62a Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 30 Oct 2024 16:22:37 +0000 Subject: [PATCH 05/20] Add mechanism evidence --- .../serializers/locus_genotype_disease.py | 64 +++++++++++++------ .../gene2phenotype_app/views/publication.py | 7 +- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py index e125348d..e1b2591c 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py @@ -576,7 +576,36 @@ def update_mechanism(self, lgd_instance, validated_data): # Get evidence - the mechanism evidence was validated in the view 'LGDUpdateMechanism' # Example: {'pmid': '25099252', 'description': 'text', 'evidence_types': # [{'primary_type': 'Rescue', 'secondary_type': ['Human', 'Patient Cells']}]} - for evidence in mechanism_evidence: + lgd_instance.update_mechanism_evidence(mechanism_obj, mechanism_evidence) + + + # Save old molecular mechanism to delete it later + old_mechanism_obj = lgd_instance.molecular_mechanism + + # Update LGD record + lgd_instance.molecular_mechanism = mechanism_obj + lgd_instance.date_review = datetime.now() + lgd_instance.save() + + # The old molecular mechanism can be deleted - the deletion is stored in the history table + # As this method only allows to update 'undetermined' mechanisms, there is no evidence to be deleted + old_mechanism_obj.delete() + + return lgd_instance + + def update_mechanism_evidence(self, mechanism_obj, validated_data): + """ + Method to only update the evidence of the LGD molecular mechanism. + + 'validated_data' example: + "mechanism_evidence": [{ + "pmid": "1234", + "description": "This is new evidence for the existing mechanism evidence.", + "evidence_types": [ { "primary_type": "Function", + "secondary_type": [ "Biochemical" ]} + ]}] + """ + for evidence in validated_data: pmid = evidence.get("pmid") # Check if the PMID exists in G2P @@ -613,27 +642,24 @@ def update_mechanism(self, lgd_instance, validated_data): raise serializers.ValidationError({"message": f"Invalid mechanism evidence '{m_type}'"}) # Insert evidence - mechanism_evidence_obj = MolecularMechanismEvidence.objects.create( + try: + mechanism_evidence_obj = MolecularMechanismEvidence.objects.get( molecular_mechanism = mechanism_obj, - description = description, publication = publication_obj, - evidence = cv_evidence_obj, - is_deleted = 0 + evidence = cv_evidence_obj ) - - # Save old molecular mechanism to delete it later - old_mechanism_obj = lgd_instance.molecular_mechanism - - # Update LGD record - lgd_instance.molecular_mechanism = mechanism_obj - lgd_instance.date_review = datetime.now() - lgd_instance.save() - - # The old molecular mechanism can be deleted - the deletion is stored in the history table - # As this method only allows to update 'undetermined' mechanisms, there is no evidence to be deleted - old_mechanism_obj.delete() - - return lgd_instance + except MolecularMechanismEvidence.DoesNotExist: + mechanism_evidence_obj = MolecularMechanismEvidence.objects.create( + molecular_mechanism = mechanism_obj, + description = description, + publication = publication_obj, + evidence = cv_evidence_obj, + is_deleted = 0 + ) + else: + if(mechanism_evidence_obj.is_deleted == 1): + mechanism_evidence_obj.is_deleted = 0 + mechanism_evidence_obj.save() class Meta: model = LocusGenotypeDisease diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 6424f7ee..4000dbb3 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -7,7 +7,8 @@ from gene2phenotype_app.serializers import (PublicationSerializer, LGDPublicationSerializer, LGDPublicationListSerializer, LGDPhenotypeSerializer, - LGDVariantTypeSerializer, LGDVariantTypeDescriptionSerializer) + LGDVariantTypeSerializer, LGDVariantTypeDescriptionSerializer, + LocusGenotypeDiseaseSerializer) from gene2phenotype_app.models import (Publication, LocusGenotypeDisease, LGDPublication, LGDPhenotype, LGDPhenotypeSummary, LGDVariantType, @@ -252,7 +253,9 @@ def post(self, request, stable_id): LGDVariantTypeDescriptionSerializer(context={'lgd': lgd}).create(variant_type_desc) if "mechanism_evidence" in publication: - print("Add mechanism evidence!!") + lgd_serializer = LocusGenotypeDiseaseSerializer() + mechanism_obj = lgd.molecular_mechanism + lgd_serializer.update_mechanism_evidence(mechanism_obj, publication["mechanism_evidence"]) response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) From 1ba91f9a3099877f1b538136b9e5ffdbc0b99739 Mon Sep 17 00:00:00 2001 From: dglemos Date: Thu, 7 Nov 2024 13:50:20 +0000 Subject: [PATCH 06/20] Support mechanism update --- .../serializers/locus_genotype_disease.py | 10 ++- .../serializers/publication.py | 13 ++-- .../gene2phenotype_app/views/publication.py | 67 +++++++++++++++++-- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py index e1b2591c..d359635e 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py @@ -576,8 +576,7 @@ def update_mechanism(self, lgd_instance, validated_data): # Get evidence - the mechanism evidence was validated in the view 'LGDUpdateMechanism' # Example: {'pmid': '25099252', 'description': 'text', 'evidence_types': # [{'primary_type': 'Rescue', 'secondary_type': ['Human', 'Patient Cells']}]} - lgd_instance.update_mechanism_evidence(mechanism_obj, mechanism_evidence) - + lgd_instance.update_mechanism_evidence(lgd_instance, mechanism_evidence) # Save old molecular mechanism to delete it later old_mechanism_obj = lgd_instance.molecular_mechanism @@ -593,7 +592,7 @@ def update_mechanism(self, lgd_instance, validated_data): return lgd_instance - def update_mechanism_evidence(self, mechanism_obj, validated_data): + def update_mechanism_evidence(self, lgd_obj, validated_data): """ Method to only update the evidence of the LGD molecular mechanism. @@ -605,6 +604,7 @@ def update_mechanism_evidence(self, mechanism_obj, validated_data): "secondary_type": [ "Biochemical" ]} ]}] """ + mechanism_obj = lgd_obj.molecular_mechanism for evidence in validated_data: pmid = evidence.get("pmid") @@ -661,6 +661,10 @@ def update_mechanism_evidence(self, mechanism_obj, validated_data): mechanism_evidence_obj.is_deleted = 0 mechanism_evidence_obj.save() + # Update LGD date_review + lgd_obj.date_review = datetime.now() + lgd_obj.save() + class Meta: model = LocusGenotypeDisease exclude = ['id', 'is_deleted', 'date_review'] diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py index e1ce9438..01f602a3 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py @@ -374,6 +374,11 @@ def create(self, validated_data): publication = publication_obj, is_deleted = 0 ) + # There is a new publication linked to the LGD record, + # the record has to reflect the date of this change + # update the date_review of the LGD record + lgd.date_review = datetime.now() + lgd.save() # If LGD-publication already exists then returns the existing object # New comments and/or family info are added to the existing object @@ -381,10 +386,10 @@ def create(self, validated_data): if lgd_publication_obj.is_deleted != 0: lgd_publication_obj.is_deleted = 0 lgd_publication_obj.save() - - # When we add a publication to a LGD record, we should also link the new publication to - # other existing/new data (LGDPhenotype, LGDPhenotypeSummary, LGDVariantType, LGDVariantTypeDescription) - + # The lgd-publication is not deleted anymore which is equivallent to + # creating a new link, this means we have to update the record 'date_review' + lgd.date_review = datetime.now() + lgd.save() return lgd_publication_obj diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 4000dbb3..ede64155 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -14,7 +14,7 @@ LGDPhenotype, LGDPhenotypeSummary, LGDVariantType, LGDVariantTypeDescription, MolecularMechanismEvidence) -from .base import BaseAdd +from .base import BaseAdd, BaseUpdate from ..utils import get_publication, get_authors @@ -108,7 +108,7 @@ class AddPublication(BaseAdd): ### LGD-publication ### # Add or delete data -class LGDEditPublications(APIView): +class LGDEditPublications(BaseUpdate): """ Add or delete lgd-publication. @@ -184,7 +184,15 @@ def post(self, request, stable_id): "variant_descriptions": [{ "description": "HGVS:c.9Pro", "publication": "41" - }] + }], + "molecular_mechanism": { + "name": "gain of function", + "support": "evidence" + }, + "mechanism_synopsis": { + "name": "", + "support": "" + }, "mechanism_evidence": [{ "pmid": "1234", "description": "This is new evidence for the existing mechanism evidence.", @@ -252,10 +260,59 @@ def post(self, request, stable_id): for variant_type_desc in publication["variant_descriptions"]: LGDVariantTypeDescriptionSerializer(context={'lgd': lgd}).create(variant_type_desc) - if "mechanism_evidence" in publication: + # Only mechanism "undetermined" can be updated + # If mechanism has to be updated, call method update_mechanism() and send new mechanism value + # plus the synopsis and the new evidence (if applicable) + # update_mechanism() updates the 'date_review' of the LGD record + if "molecular_mechanism" in publication: lgd_serializer = LocusGenotypeDiseaseSerializer() mechanism_obj = lgd.molecular_mechanism - lgd_serializer.update_mechanism_evidence(mechanism_obj, publication["mechanism_evidence"]) + + if mechanism_obj.mechanism.value != "undetermined": + self.handle_no_update('molecular mechanism', stable_id) + + # Build mechanism data + mechanism_data = { + "molecular_mechanism": publication["molecular_mechanism"] + } + # Attach the synopsis to be updated (if applicable) + if "mechanism_synopsis" in publication: + mechanism_data["mechanism_synopsis"] = publication["mechanism_synopsis"] + # Attach the evidence to be updated (if applicable) + if "mechanism_evidence" in publication: + mechanism_data["mechanism_evidence"] = publication["mechanism_evidence"] + + try: + lgd_serializer.update_mechanism(lgd, mechanism_data) + except Exception as e: + if hasattr(e, 'detail') and 'message' in e.detail: + return Response( + {"error": f"Error while updating molecular mechanism: {e.detail['message']}"}, + status=status.HTTP_400_BAD_REQUEST + ) + else: + return Response( + {"error": f"Error while updating molecular mechanism"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + + # If only the mechanism evidence is going to be updated, call method update_mechanism_evidence() + # update_mechanism_evidence() updates the 'date_review' of the LGD record + elif "mechanism_evidence" in publication: + lgd_serializer = LocusGenotypeDiseaseSerializer() + try: + lgd_serializer.update_mechanism_evidence(lgd, publication["mechanism_evidence"]) + except Exception as e: + if hasattr(e, 'detail') and 'message' in e.detail: + return Response( + {"error": f"Error while updating molecular mechanism evidence: {e.detail['message']}"}, + status=status.HTTP_400_BAD_REQUEST + ) + else: + return Response( + {"error": f"Error while updating molecular mechanism evidence"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) From 5d1cadd604b029a641f481d367d391509d25d1aa Mon Sep 17 00:00:00 2001 From: dglemos Date: Thu, 21 Nov 2024 18:19:08 +0000 Subject: [PATCH 07/20] Fix major bug in molecular mechanism evidence --- .../serializers/locus_genotype_disease.py | 23 +++++---- .../serializers/publication.py | 2 +- .../gene2phenotype_app/views/base.py | 7 +++ .../gene2phenotype_app/views/publication.py | 47 +++++++------------ 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py index d359635e..88d2dcba 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py @@ -573,19 +573,21 @@ def update_mechanism(self, lgd_instance, validated_data): is_deleted = 0 ) - # Get evidence - the mechanism evidence was validated in the view 'LGDUpdateMechanism' - # Example: {'pmid': '25099252', 'description': 'text', 'evidence_types': - # [{'primary_type': 'Rescue', 'secondary_type': ['Human', 'Patient Cells']}]} - lgd_instance.update_mechanism_evidence(lgd_instance, mechanism_evidence) - # Save old molecular mechanism to delete it later old_mechanism_obj = lgd_instance.molecular_mechanism # Update LGD record + # The mechanism has to be updated in the locus_genotype_disease before the evidence is added + # Because the evidence is going to be linked to the new lgd.molecular_mechanism lgd_instance.molecular_mechanism = mechanism_obj lgd_instance.date_review = datetime.now() lgd_instance.save() + # Get evidence - the mechanism evidence was validated in the view 'LGDUpdateMechanism' + # Example: {'pmid': '25099252', 'description': 'text', 'evidence_types': + # [{'primary_type': 'Rescue', 'secondary_type': ['Human', 'Patient Cells']}]} + self.update_mechanism_evidence(lgd_instance, mechanism_evidence) + # The old molecular mechanism can be deleted - the deletion is stored in the history table # As this method only allows to update 'undetermined' mechanisms, there is no evidence to be deleted old_mechanism_obj.delete() @@ -605,8 +607,9 @@ def update_mechanism_evidence(self, lgd_obj, validated_data): ]}] """ mechanism_obj = lgd_obj.molecular_mechanism + for evidence in validated_data: - pmid = evidence.get("pmid") + pmid = evidence["pmid"] # Check if the PMID exists in G2P # When updating the mechanism the supporting pmid used as evidence @@ -617,12 +620,12 @@ def update_mechanism_evidence(self, lgd_obj, validated_data): # TODO: improve in future to insert new pmids + link them to the record raise serializers.ValidationError({"message": f"pmid '{pmid}' not found in G2P"}) - if evidence.get("description") != "": - description = evidence.get("description") + if evidence["description"] != "": + description = evidence["description"] else: description = None - evidence_types = evidence.get("evidence_types") + evidence_types = evidence["evidence_types"] for evidence_type in evidence_types: # primary_type is the evidence subtype ('rescue') primary_type = evidence_type.get("primary_type", None) @@ -630,7 +633,7 @@ def update_mechanism_evidence(self, lgd_obj, validated_data): raise serializers.ValidationError({"message": f"Empty evidence subtype"}) primary_type = primary_type.lower() # secondary_type is the evidence value ('human') - secondary_type = evidence_type.get("secondary_type") + secondary_type = evidence_type["secondary_type"] for m_type in secondary_type: try: cv_evidence_obj = CVMolecularMechanism.objects.get( diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py index 01f602a3..9ea45a0f 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py @@ -340,7 +340,7 @@ def create(self, validated_data): families = self.initial_data.get("families", None) # "families": { "families": 200, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 2 } if comment: - comment_text = comment.get("comment", None) + comment_text = comment["comment"] # Check if comment text is empty string if not comment_text or comment_text == "": diff --git a/gene2phenotype_project/gene2phenotype_app/views/base.py b/gene2phenotype_project/gene2phenotype_app/views/base.py index 25cf96d5..642f7942 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/base.py +++ b/gene2phenotype_project/gene2phenotype_app/views/base.py @@ -59,6 +59,13 @@ def handle_no_update(self, data, stable_id): else: raise PermissionDenied(f"Cannot update '{data}' for ID '{stable_id}'") + def handle_update_exception(self, exception, context_message): + if hasattr(exception, 'detail') and 'message' in exception.detail: + error_message = exception.detail['message'] + return Response({"error": f"{context_message}: {error_message}"}, status=status.HTTP_400_BAD_REQUEST) + else: + error_message = context_message + return Response({"error": f"{context_message}"}, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET']) def ListEndpoints(request): diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index ede64155..018a2394 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -161,7 +161,7 @@ def post(self, request, stable_id): Example for a record already linked to pmid '41': { "publications":[ { - "publication": { "pmid": 1234 }, + "publication": { "pmid": "1234" }, "comment": { "comment": "this is a comment", "is_public": 1 }, "families": { "families": 2, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 1 }, "phenotypes": [{ @@ -221,18 +221,18 @@ def post(self, request, stable_id): if serializer_class.is_valid(): serializer_class.save() else: - response = Response({"errors": serializer_class.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + response = Response({"errors": serializer_class.errors}, status=status.HTTP_400_BAD_REQUEST) # Add extra data linked to the publication if "phenotypes" in publication: # Expected structure: # { "phenotypes": [{ "accession": "HP:0003974", "publication": 1 }] } - for pheno in publication["phenotypes"]: - hpo_terms = pheno['hpo_terms'] + for phenotype in publication["phenotypes"]: + hpo_terms = phenotype['hpo_terms'] for hpo in hpo_terms: phenotype_data = { "accession": hpo["accession"], - "publication": pheno["pmid"] + "publication": phenotype["pmid"] } try: lgd_phenotype_serializer = LGDPhenotypeSerializer( @@ -244,12 +244,12 @@ def post(self, request, stable_id): # save() is going to call create() lgd_phenotype_serializer.save() else: - response = Response({"errors": lgd_phenotype_serializer.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + response = Response({"errors": lgd_phenotype_serializer.errors}, status=status.HTTP_400_BAD_REQUEST) except: accession = phenotype_data["accession"] return Response( {"errors": f"Could not insert phenotype '{accession}' for ID '{stable_id}'"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR + status=status.HTTP_400_BAD_REQUEST ) if "variant_types" in publication: @@ -268,9 +268,13 @@ def post(self, request, stable_id): lgd_serializer = LocusGenotypeDiseaseSerializer() mechanism_obj = lgd.molecular_mechanism - if mechanism_obj.mechanism.value != "undetermined": + print("->", mechanism_obj.mechanism.value) + print("->", mechanism_obj.mechanism_support.value) + + if(mechanism_obj.mechanism.value != "undetermined" or + mechanism_obj.mechanism_support.value != "inferred"): self.handle_no_update('molecular mechanism', stable_id) - + # Build mechanism data mechanism_data = { "molecular_mechanism": publication["molecular_mechanism"] @@ -285,39 +289,22 @@ def post(self, request, stable_id): try: lgd_serializer.update_mechanism(lgd, mechanism_data) except Exception as e: - if hasattr(e, 'detail') and 'message' in e.detail: - return Response( - {"error": f"Error while updating molecular mechanism: {e.detail['message']}"}, - status=status.HTTP_400_BAD_REQUEST - ) - else: - return Response( - {"error": f"Error while updating molecular mechanism"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) + return self.handle_update_exception(e, "Error while updating molecular mechanism") # If only the mechanism evidence is going to be updated, call method update_mechanism_evidence() # update_mechanism_evidence() updates the 'date_review' of the LGD record + # TODO: but before adding evidence we have to check/update the mechanism support to "evidence" elif "mechanism_evidence" in publication: lgd_serializer = LocusGenotypeDiseaseSerializer() try: lgd_serializer.update_mechanism_evidence(lgd, publication["mechanism_evidence"]) except Exception as e: - if hasattr(e, 'detail') and 'message' in e.detail: - return Response( - {"error": f"Error while updating molecular mechanism evidence: {e.detail['message']}"}, - status=status.HTTP_400_BAD_REQUEST - ) - else: - return Response( - {"error": f"Error while updating molecular mechanism evidence"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) + return self.handle_update_exception(e, "Error while updating molecular mechanism evidence") response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) else: - response = Response({"errors": serializer_list.errors}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + response = Response({"errors": serializer_list.errors}, status=status.HTTP_400_BAD_REQUEST) return response From 2437c1bb4de229e8e17525bb999c36285c44e8db Mon Sep 17 00:00:00 2001 From: dglemos Date: Thu, 21 Nov 2024 18:22:13 +0000 Subject: [PATCH 08/20] Remove print --- gene2phenotype_project/gene2phenotype_app/views/publication.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 018a2394..e5a523ba 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -268,9 +268,6 @@ def post(self, request, stable_id): lgd_serializer = LocusGenotypeDiseaseSerializer() mechanism_obj = lgd.molecular_mechanism - print("->", mechanism_obj.mechanism.value) - print("->", mechanism_obj.mechanism_support.value) - if(mechanism_obj.mechanism.value != "undetermined" or mechanism_obj.mechanism_support.value != "inferred"): self.handle_no_update('molecular mechanism', stable_id) From 687e8bdb9b2cf33c5aada37611fc854c72b22475 Mon Sep 17 00:00:00 2001 From: dglemos Date: Tue, 26 Nov 2024 15:40:34 +0000 Subject: [PATCH 09/20] Update input format --- .../gene2phenotype_app/views/publication.py | 156 +++++++++--------- 1 file changed, 81 insertions(+), 75 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index e5a523ba..762efcdb 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -209,7 +209,13 @@ def post(self, request, stable_id): serializer_list = LGDPublicationListSerializer(data=request.data) if serializer_list.is_valid(): - publications_data = serializer_list.validated_data.get('publications') + publications_data = serializer_list.validated_data.get('publications') # the pmids are mandatory + phenotypes_data = serializer_list.validated_data.get('phenotypes', None) # optional + variant_types_data = serializer_list.validated_data.get('variant_types', None) # optional + variant_descriptions_data = serializer_list.validated_data.get('variant_descriptions', None) # optional + mechanism_data = serializer_list.validated_data.get('molecular_mechanism', None) # optional + mechanism_synopsis_data = serializer_list.validated_data.get('mechanism_synopsis', None) # optional + mechanism_evidence_data = serializer_list.validated_data.get('mechanism_evidence', None) # optional for publication in publications_data: serializer_class = LGDPublicationSerializer( @@ -223,82 +229,82 @@ def post(self, request, stable_id): else: response = Response({"errors": serializer_class.errors}, status=status.HTTP_400_BAD_REQUEST) - # Add extra data linked to the publication - if "phenotypes" in publication: - # Expected structure: - # { "phenotypes": [{ "accession": "HP:0003974", "publication": 1 }] } - for phenotype in publication["phenotypes"]: - hpo_terms = phenotype['hpo_terms'] - for hpo in hpo_terms: - phenotype_data = { - "accession": hpo["accession"], - "publication": phenotype["pmid"] - } - try: - lgd_phenotype_serializer = LGDPhenotypeSerializer( - data = phenotype_data, - context = {'lgd': lgd} - ) - # Validate the input data - if lgd_phenotype_serializer.is_valid(): - # save() is going to call create() - lgd_phenotype_serializer.save() - else: - response = Response({"errors": lgd_phenotype_serializer.errors}, status=status.HTTP_400_BAD_REQUEST) - except: - accession = phenotype_data["accession"] - return Response( - {"errors": f"Could not insert phenotype '{accession}' for ID '{stable_id}'"}, - status=status.HTTP_400_BAD_REQUEST - ) - - if "variant_types" in publication: - for variant_type in publication["variant_types"]: - LGDVariantTypeSerializer(context={'lgd': lgd, 'user': user}).create(variant_type) - - if "variant_descriptions" in publication: - for variant_type_desc in publication["variant_descriptions"]: - LGDVariantTypeDescriptionSerializer(context={'lgd': lgd}).create(variant_type_desc) - - # Only mechanism "undetermined" can be updated - # If mechanism has to be updated, call method update_mechanism() and send new mechanism value - # plus the synopsis and the new evidence (if applicable) - # update_mechanism() updates the 'date_review' of the LGD record - if "molecular_mechanism" in publication: - lgd_serializer = LocusGenotypeDiseaseSerializer() - mechanism_obj = lgd.molecular_mechanism - - if(mechanism_obj.mechanism.value != "undetermined" or - mechanism_obj.mechanism_support.value != "inferred"): - self.handle_no_update('molecular mechanism', stable_id) - - # Build mechanism data - mechanism_data = { - "molecular_mechanism": publication["molecular_mechanism"] + # Add extra data linked to the publication - phenotypes + # Expected structure: + # { "phenotypes": [{ "accession": "HP:0003974", "publication": 1 }] } + for phenotype in phenotypes_data: + hpo_terms = phenotype['hpo_terms'] + for hpo in hpo_terms: + phenotype_data = { + "accession": hpo["accession"], + "publication": phenotype["pmid"] } - # Attach the synopsis to be updated (if applicable) - if "mechanism_synopsis" in publication: - mechanism_data["mechanism_synopsis"] = publication["mechanism_synopsis"] - # Attach the evidence to be updated (if applicable) - if "mechanism_evidence" in publication: - mechanism_data["mechanism_evidence"] = publication["mechanism_evidence"] - - try: - lgd_serializer.update_mechanism(lgd, mechanism_data) - except Exception as e: - return self.handle_update_exception(e, "Error while updating molecular mechanism") - - # If only the mechanism evidence is going to be updated, call method update_mechanism_evidence() - # update_mechanism_evidence() updates the 'date_review' of the LGD record - # TODO: but before adding evidence we have to check/update the mechanism support to "evidence" - elif "mechanism_evidence" in publication: - lgd_serializer = LocusGenotypeDiseaseSerializer() try: - lgd_serializer.update_mechanism_evidence(lgd, publication["mechanism_evidence"]) - except Exception as e: - return self.handle_update_exception(e, "Error while updating molecular mechanism evidence") - - response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) + lgd_phenotype_serializer = LGDPhenotypeSerializer( + data = phenotype_data, + context = {'lgd': lgd} + ) + # Validate the input data + if lgd_phenotype_serializer.is_valid(): + # save() is going to call create() + lgd_phenotype_serializer.save() + else: + response = Response({"errors": lgd_phenotype_serializer.errors}, status=status.HTTP_400_BAD_REQUEST) + except: + accession = phenotype_data["accession"] + return Response( + {"errors": f"Could not insert phenotype '{accession}' for ID '{stable_id}'"}, + status=status.HTTP_400_BAD_REQUEST + ) + + # Add extra data linked to the publication - variant types + for variant_type in variant_types_data: + LGDVariantTypeSerializer(context={'lgd': lgd, 'user': user}).create(variant_type) + + # Add extra data linked to the publication - variant descriptions (HGVS) + for variant_type_desc in variant_descriptions_data: + LGDVariantTypeDescriptionSerializer(context={'lgd': lgd}).create(variant_type_desc) + + # Only mechanism "undetermined" can be updated + # If mechanism has to be updated, call method update_mechanism() and send new mechanism value + # plus the synopsis and the new evidence (if applicable) + # update_mechanism() updates the 'date_review' of the LGD record + if mechanism_data: + lgd_serializer = LocusGenotypeDiseaseSerializer() + mechanism_obj = lgd.molecular_mechanism + + # Check if it's possible to update the mechanism + if(mechanism_obj.mechanism.value != "undetermined" or + mechanism_obj.mechanism_support.value != "inferred"): + self.handle_no_update('molecular mechanism', stable_id) + + # Build mechanism data + mechanism_data_input = { + "molecular_mechanism": mechanism_data + } + # Attach the synopsis to be updated (if applicable) + if mechanism_synopsis_data: + mechanism_data_input["mechanism_synopsis"] = mechanism_synopsis_data + # Attach the evidence to be updated (if applicable) + if mechanism_evidence_data: + mechanism_data_input["mechanism_evidence"] = mechanism_evidence_data + + try: + lgd_serializer.update_mechanism(lgd, mechanism_data_input) + except Exception as e: + return self.handle_update_exception(e, "Error while updating molecular mechanism") + + # If only the mechanism evidence is going to be updated, call method update_mechanism_evidence() + # update_mechanism_evidence() updates the 'date_review' of the LGD record + # TODO: but before adding evidence we have to check/update the mechanism support to "evidence" + elif mechanism_evidence_data: + lgd_serializer = LocusGenotypeDiseaseSerializer() + try: + lgd_serializer.update_mechanism_evidence(lgd, mechanism_evidence_data) + except Exception as e: + return self.handle_update_exception(e, "Error while updating molecular mechanism evidence") + + response = Response({'message': 'Publication added to the G2P entry successfully.'}, status=status.HTTP_201_CREATED) else: response = Response({"errors": serializer_list.errors}, status=status.HTTP_400_BAD_REQUEST) From cc890114baf94f583242c67c413d5828dce1b809 Mon Sep 17 00:00:00 2001 From: dglemos Date: Tue, 26 Nov 2024 15:52:26 +0000 Subject: [PATCH 10/20] Improve mechanism synopsis code --- .../serializers/locus_genotype_disease.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py index 88d2dcba..da9a07e8 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py @@ -544,9 +544,9 @@ def update_mechanism(self, lgd_instance, validated_data): # The mechanism synopsis is optional cv_synopsis_obj = None cv_synopsis_support_obj = None - if mechanism_synopsis is not None and mechanism_synopsis.get("name", "") != "": - mechanism_synopsis_value = mechanism_synopsis.get("name") - mechanism_synopsis_support = mechanism_synopsis.get("support") + if mechanism_synopsis and mechanism_synopsis["name"] != "": + mechanism_synopsis_value = mechanism_synopsis["name"] + mechanism_synopsis_support = mechanism_synopsis["support"] try: cv_synopsis_obj = CVMolecularMechanism.objects.get( From fcc87300e53d7f45ac3cf207ed7ef1b18838b5f1 Mon Sep 17 00:00:00 2001 From: dglemos Date: Tue, 26 Nov 2024 17:46:25 +0000 Subject: [PATCH 11/20] Update example in docs --- .../gene2phenotype_app/views/publication.py | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 762efcdb..6619fcd7 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -171,35 +171,36 @@ def post(self, request, stable_id): "accession": "HP:0002310", "description": "" }] }], - "variant_types": [{ - "comment": "", - "de_novo": false, - "inherited": false, - "nmd_escape": false, - "primary_type": "protein_changing", - "secondary_type": "inframe_insertion", - "supporting_papers": ["41"], - "unknown_inheritance": true - }], - "variant_descriptions": [{ - "description": "HGVS:c.9Pro", - "publication": "41" - }], - "molecular_mechanism": { - "name": "gain of function", - "support": "evidence" - }, - "mechanism_synopsis": { - "name": "", - "support": "" - }, - "mechanism_evidence": [{ - "pmid": "1234", - "description": "This is new evidence for the existing mechanism evidence.", - "evidence_types": [ { "primary_type": "Function", - "secondary_type": [ "Biochemical" ]} - ]}] - }]} + }], + "variant_types": [{ + "comment": "", + "de_novo": false, + "inherited": false, + "nmd_escape": false, + "primary_type": "protein_changing", + "secondary_type": "inframe_insertion", + "supporting_papers": ["41"], + "unknown_inheritance": true + }], + "variant_descriptions": [{ + "description": "HGVS:c.9Pro", + "publication": "41" + }], + "molecular_mechanism": { + "name": "gain of function", + "support": "evidence" + }, + "mechanism_synopsis": { + "name": "", + "support": "" + }, + "mechanism_evidence": [{ + "pmid": "1234", + "description": "This is new evidence for the existing mechanism evidence.", + "evidence_types": [ { "primary_type": "Function", + "secondary_type": [ "Biochemical" ]} + ]}] + } """ user = self.request.user From 643676137419fbf54bb58472bbc1adef8628d275 Mon Sep 17 00:00:00 2001 From: dglemos Date: Fri, 29 Nov 2024 12:58:25 +0000 Subject: [PATCH 12/20] Fix variant types update --- .../serializers/locus_genotype_disease.py | 57 ++++++++++++++----- .../gene2phenotype_app/views/publication.py | 6 +- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py index aaa9287b..1016d782 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/locus_genotype_disease.py @@ -163,7 +163,15 @@ def get_variant_type(self, id): accession = lgd_variant.variant_type_ot.accession if accession in data and lgd_variant.publication: + # Add pmid to list of publications data[accession]["publications"].append(lgd_variant.publication.pmid) + # Check the variant inheritance - we group this data for each publication + if(lgd_variant.inherited is True): + data[accession]["inherited"] = lgd_variant.inherited + if(lgd_variant.de_novo is True): + data[accession]["de_novo"] = lgd_variant.de_novo + if(lgd_variant.unknown_inheritance is True): + data[accession]["unknown_inheritance"] = lgd_variant.unknown_inheritance else: publication_list = [] if lgd_variant.publication: @@ -1116,15 +1124,13 @@ def create(self, validated_data): # Variants are supposed to be linked to publications # But if there is no publication then create the object without a pmid if not publications: + # Check if entry exists in the table + # An unique entry is defined by: lgd, variant_type and publication try: lgd_variant_type_obj = LGDVariantType.objects.get( lgd = lgd, variant_type_ot = var_type_obj, - inherited = inherited, - de_novo = de_novo, - unknown_inheritance = unknown_inheritance, - publication = None, - is_deleted = 0 + publication = None ) except LGDVariantType.DoesNotExist: lgd_variant_type_obj = LGDVariantType.objects.create( @@ -1135,6 +1141,19 @@ def create(self, validated_data): unknown_inheritance = unknown_inheritance, is_deleted = 0 ) + else: + # the entry already exists, it probably needs to be updated + # if deleted, set to not deleted + if(lgd_variant_type_obj.is_deleted == 1): + lgd_variant_type_obj.is_deleted = 0 + # update inheritance data + if(lgd_variant_type_obj.inherited == 0 and inherited == True): + lgd_variant_type_obj.inherited = 1 + if(lgd_variant_type_obj.de_novo == 0 and de_novo == True): + lgd_variant_type_obj.de_novo = 1 + if(lgd_variant_type_obj.unknown_inheritance == 0 and unknown_inheritance == True): + lgd_variant_type_obj.unknown_inheritance = 1 + lgd_variant_type_obj.save() # The LGDPhenotypeSummary is created - next step is to create the LGDVariantTypeComment if(comment != ""): @@ -1179,11 +1198,7 @@ def create(self, validated_data): lgd_variant_type_obj = LGDVariantType.objects.get( lgd = lgd, variant_type_ot = var_type_obj, - inherited = inherited, - de_novo = de_novo, - unknown_inheritance = unknown_inheritance, - publication = publication_obj, - is_deleted = 0 + publication = publication_obj ) except LGDVariantType.DoesNotExist: # Check if LGDVariantType object already exists in db without a publication @@ -1191,9 +1206,6 @@ def create(self, validated_data): lgd_variant_type_obj = LGDVariantType.objects.get( lgd = lgd, variant_type_ot = var_type_obj, - inherited = inherited, - de_novo = de_novo, - unknown_inheritance = unknown_inheritance, publication = None, # no publication attached to entry is_deleted = 0 ) @@ -1213,7 +1225,26 @@ def create(self, validated_data): # LGDVariantType already exists in the db without a publication # Add publication to existing object lgd_variant_type_obj.publication = publication_obj + if(lgd_variant_type_obj.inherited == False and inherited == True): + lgd_variant_type_obj.inherited = True + if(lgd_variant_type_obj.de_novo == False and de_novo == True): + lgd_variant_type_obj.de_novo = True + if(lgd_variant_type_obj.unknown_inheritance == False and unknown_inheritance == True): + lgd_variant_type_obj.unknown_inheritance = True lgd_variant_type_obj.save() + else: + # the entry already exists, it probably needs to be updated + # if deleted, set to not deleted + if(lgd_variant_type_obj.is_deleted == 1): + lgd_variant_type_obj.is_deleted = 0 + # update inheritance data + if(lgd_variant_type_obj.inherited == False and inherited == True): + lgd_variant_type_obj.inherited = True + if(lgd_variant_type_obj.de_novo == False and de_novo == True): + lgd_variant_type_obj.de_novo = True + if(lgd_variant_type_obj.unknown_inheritance == False and unknown_inheritance == True): + lgd_variant_type_obj.unknown_inheritance = True + lgd_variant_type_obj.save() # The LGDPhenotypeSummary is created - next step is to create the LGDVariantTypeComment if(comment != ""): diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 6619fcd7..525feca4 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -211,9 +211,9 @@ def post(self, request, stable_id): if serializer_list.is_valid(): publications_data = serializer_list.validated_data.get('publications') # the pmids are mandatory - phenotypes_data = serializer_list.validated_data.get('phenotypes', None) # optional - variant_types_data = serializer_list.validated_data.get('variant_types', None) # optional - variant_descriptions_data = serializer_list.validated_data.get('variant_descriptions', None) # optional + phenotypes_data = serializer_list.validated_data.get('phenotypes', []) # optional + variant_types_data = serializer_list.validated_data.get('variant_types', []) # optional + variant_descriptions_data = serializer_list.validated_data.get('variant_descriptions', []) # optional mechanism_data = serializer_list.validated_data.get('molecular_mechanism', None) # optional mechanism_synopsis_data = serializer_list.validated_data.get('mechanism_synopsis', None) # optional mechanism_evidence_data = serializer_list.validated_data.get('mechanism_evidence', None) # optional From 1bdf51604976594e58c8696f59983b6a6b6ff26c Mon Sep 17 00:00:00 2001 From: dglemos Date: Tue, 3 Dec 2024 13:32:53 +0000 Subject: [PATCH 13/20] Update publication families - only save if number of families is defined --- .../serializers/publication.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py index 9ea45a0f..6ff3334d 100644 --- a/gene2phenotype_project/gene2phenotype_app/serializers/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/serializers/publication.py @@ -218,11 +218,11 @@ def create(self, validated_data): families to the existing PMID. This method is called when publishing a record. - The extra fields 'comment' and 'families' are passed in the context. + The PMID is mandatory, while comment and families are optional. Args: (dict) validated_data: valid PublicationSerializer fields - accepted fields are: pmid, title, authors, year + accepted fields are: pmid, comment, families Returns: Publication object @@ -230,11 +230,11 @@ def create(self, validated_data): Raises: Invalid PMID """ - pmid = validated_data.get('pmid') # serializer fields - comment = validated_data.get('comment') # extra data - comment - number_of_families = validated_data.get('families') # extra data - families reported in publication + pmid = validated_data['pmid'] # serializer fields + comment = validated_data['comment'] # extra data - comment + number_of_families = validated_data['families'] # extra data - families reported in publication - user_obj = self.context.get('user') + user_obj = self.context['user'] try: publication_obj = Publication.objects.get(pmid=pmid) @@ -271,8 +271,9 @@ def create(self, validated_data): context={'user': user_obj} ).create(comment, publication_obj) - # Add family info linked to publication - if number_of_families is not None: + # Add family info linked to publication - if there are no families reported, then we don't save the data + # Example: number_of_families = {'families': 20, 'consanguinity': 'unknown', 'ancestries': '', 'affected_individuals': None} + if number_of_families and number_of_families["families"]: PublicationFamiliesSerializer().create(number_of_families, publication_obj) return publication_obj @@ -308,8 +309,8 @@ def validate(self, data): # correct structure is: # { "families": 200, "consanguinity": "unknown", "ancestries": "african", # "affected_individuals": 100 } - if "families" in self.initial_data: - families = self.initial_data.get("families") # If 'families' is in initial data then it cannot be null + if "families" in self.initial_data and self.initial_data["families"] is not None: + families = self.initial_data["families"] # If 'families' is in initial data then it cannot be null for header in families.keys(): if header not in valid_headers: raise serializers.ValidationError(f"Got unknown field in families: {header}") From b9239a3033752e8303db4f9b07ca66bd496b3f5c Mon Sep 17 00:00:00 2001 From: dglemos Date: Tue, 3 Dec 2024 15:05:17 +0000 Subject: [PATCH 14/20] Add test --- .../tests/views/test_molecular_mechanism.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 gene2phenotype_project/gene2phenotype_app/tests/views/test_molecular_mechanism.py diff --git a/gene2phenotype_project/gene2phenotype_app/tests/views/test_molecular_mechanism.py b/gene2phenotype_project/gene2phenotype_app/tests/views/test_molecular_mechanism.py new file mode 100644 index 00000000..de2af038 --- /dev/null +++ b/gene2phenotype_project/gene2phenotype_app/tests/views/test_molecular_mechanism.py @@ -0,0 +1,23 @@ +from django.test import TestCase +from django.urls import reverse + +class ListMolecularMechanismsEndpoint(TestCase): + """ + Test endpoint that returns the mechanism values + """ + fixtures = ["gene2phenotype_app/fixtures/cv_molecular_mechanism.json"] + + def setUp(self): + self.url_list_mechanisms = reverse('list_mechanisms') + + def test_mechanism_list(self): + """ + Test the cv_molecular_mechanism data + """ + response = self.client.get(self.url_list_mechanisms) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 4) # there are 4 types of mechanism data + self.assertEqual(len(response.data["mechanism"]), 5) + self.assertEqual(len(response.data["mechanism_synopsis"]), 10) + self.assertEqual(len(response.data["support"]), 2) + self.assertEqual(len(response.data["evidence"]), 4) From 467c16f3c9615365d28c754cdbddeaa74e1bb53e Mon Sep 17 00:00:00 2001 From: dglemos Date: Tue, 3 Dec 2024 16:09:07 +0000 Subject: [PATCH 15/20] Add variant type test --- .../tests/views/test_variant_type.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 gene2phenotype_project/gene2phenotype_app/tests/views/test_variant_type.py diff --git a/gene2phenotype_project/gene2phenotype_app/tests/views/test_variant_type.py b/gene2phenotype_project/gene2phenotype_app/tests/views/test_variant_type.py new file mode 100644 index 00000000..d555bf24 --- /dev/null +++ b/gene2phenotype_project/gene2phenotype_app/tests/views/test_variant_type.py @@ -0,0 +1,25 @@ +from django.test import TestCase +from django.urls import reverse + +class ListVariantTypesEndpoint(TestCase): + """ + Test endpoint that returns the ontology terms for variant type + """ + fixtures = ["gene2phenotype_app/fixtures/ontology_term.json", "gene2phenotype_app/fixtures/source.json", + "gene2phenotype_app/fixtures/attribs.json"] + + def setUp(self): + self.url_list_variant_types = reverse('list_variant_types') + + def test_variant_types(self): + """ + Test the ontology types + """ + response = self.client.get(self.url_list_variant_types) + self.assertEqual(response.status_code, 200) + # Counts the number of terms by variant type + self.assertEqual(len(response.data["NMD_variants"]), 10) + self.assertEqual(len(response.data["splice_variants"]), 3) + self.assertEqual(len(response.data["protein_changing_variants"]), 7) + self.assertEqual(len(response.data["regulatory_variants"]), 3) + self.assertEqual(len(response.data["other_variants"]), 8) From 13ea9aadf1a0bd895d8b5ea9a063fcda3614cd4f Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 4 Dec 2024 12:52:10 +0000 Subject: [PATCH 16/20] Change mechanism permission --- .../gene2phenotype_app/views/publication.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 525feca4..2ec79514 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -274,15 +274,13 @@ def post(self, request, stable_id): lgd_serializer = LocusGenotypeDiseaseSerializer() mechanism_obj = lgd.molecular_mechanism - # Check if it's possible to update the mechanism - if(mechanism_obj.mechanism.value != "undetermined" or - mechanism_obj.mechanism_support.value != "inferred"): - self.handle_no_update('molecular mechanism', stable_id) - # Build mechanism data - mechanism_data_input = { - "molecular_mechanism": mechanism_data - } + mechanism_data_input = {} + + # Check if it's possible to update the mechanism value + if mechanism_obj.mechanism.value != "undetermined": + mechanism_data_input["molecular_mechanism"] = mechanism_data + # Attach the synopsis to be updated (if applicable) if mechanism_synopsis_data: mechanism_data_input["mechanism_synopsis"] = mechanism_synopsis_data From 91605ea4dd90865c42d49ab88871b4416f95f55b Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 4 Dec 2024 15:49:42 +0000 Subject: [PATCH 17/20] Remove check --- .../gene2phenotype_app/views/publication.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 2ec79514..c40079d0 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -272,14 +272,11 @@ def post(self, request, stable_id): # update_mechanism() updates the 'date_review' of the LGD record if mechanism_data: lgd_serializer = LocusGenotypeDiseaseSerializer() - mechanism_obj = lgd.molecular_mechanism # Build mechanism data - mechanism_data_input = {} - - # Check if it's possible to update the mechanism value - if mechanism_obj.mechanism.value != "undetermined": - mechanism_data_input["molecular_mechanism"] = mechanism_data + mechanism_data_input = { + "molecular_mechanism": mechanism_data + } # Attach the synopsis to be updated (if applicable) if mechanism_synopsis_data: From 9dd5f46c68116ab067c64e9c30d814db7ca1ab2b Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 4 Dec 2024 15:59:42 +0000 Subject: [PATCH 18/20] Revert "Remove check" This reverts commit 91605ea4dd90865c42d49ab88871b4416f95f55b. --- .../gene2phenotype_app/views/publication.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index c40079d0..2ec79514 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -272,11 +272,14 @@ def post(self, request, stable_id): # update_mechanism() updates the 'date_review' of the LGD record if mechanism_data: lgd_serializer = LocusGenotypeDiseaseSerializer() + mechanism_obj = lgd.molecular_mechanism # Build mechanism data - mechanism_data_input = { - "molecular_mechanism": mechanism_data - } + mechanism_data_input = {} + + # Check if it's possible to update the mechanism value + if mechanism_obj.mechanism.value != "undetermined": + mechanism_data_input["molecular_mechanism"] = mechanism_data # Attach the synopsis to be updated (if applicable) if mechanism_synopsis_data: From d6d00ac62ae39d135477f53273d531b0042d9c3f Mon Sep 17 00:00:00 2001 From: dglemos Date: Wed, 4 Dec 2024 15:59:52 +0000 Subject: [PATCH 19/20] Revert "Change mechanism permission" This reverts commit 13ea9aadf1a0bd895d8b5ea9a063fcda3614cd4f. --- .../gene2phenotype_app/views/publication.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 2ec79514..525feca4 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -274,13 +274,15 @@ def post(self, request, stable_id): lgd_serializer = LocusGenotypeDiseaseSerializer() mechanism_obj = lgd.molecular_mechanism - # Build mechanism data - mechanism_data_input = {} - - # Check if it's possible to update the mechanism value - if mechanism_obj.mechanism.value != "undetermined": - mechanism_data_input["molecular_mechanism"] = mechanism_data + # Check if it's possible to update the mechanism + if(mechanism_obj.mechanism.value != "undetermined" or + mechanism_obj.mechanism_support.value != "inferred"): + self.handle_no_update('molecular mechanism', stable_id) + # Build mechanism data + mechanism_data_input = { + "molecular_mechanism": mechanism_data + } # Attach the synopsis to be updated (if applicable) if mechanism_synopsis_data: mechanism_data_input["mechanism_synopsis"] = mechanism_synopsis_data From 6ca4ec736f54749894c19ec926e6a62dc777ac70 Mon Sep 17 00:00:00 2001 From: dglemos Date: Thu, 5 Dec 2024 16:13:21 +0000 Subject: [PATCH 20/20] Update comment --- .../gene2phenotype_app/views/publication.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gene2phenotype_project/gene2phenotype_app/views/publication.py b/gene2phenotype_project/gene2phenotype_app/views/publication.py index 525feca4..c43a3675 100644 --- a/gene2phenotype_project/gene2phenotype_app/views/publication.py +++ b/gene2phenotype_project/gene2phenotype_app/views/publication.py @@ -164,14 +164,14 @@ def post(self, request, stable_id): "publication": { "pmid": "1234" }, "comment": { "comment": "this is a comment", "is_public": 1 }, "families": { "families": 2, "consanguinity": "unknown", "ancestries": "african", "affected_individuals": 1 }, - "phenotypes": [{ - "pmid": "41", - "summary": "", - "hpo_terms": [{ "term": "Orofacial dyskinesia", - "accession": "HP:0002310", - "description": "" }] - }], }], + "phenotypes": [{ + "pmid": "41", + "summary": "", + "hpo_terms": [{ "term": "Orofacial dyskinesia", + "accession": "HP:0002310", + "description": "" }] + }], "variant_types": [{ "comment": "", "de_novo": false,