diff --git a/resolwe_bio/variants/listener_plugin.py b/resolwe_bio/variants/listener_plugin.py index 436775d62..32af343e3 100644 --- a/resolwe_bio/variants/listener_plugin.py +++ b/resolwe_bio/variants/listener_plugin.py @@ -16,6 +16,7 @@ VariantAnnotationTranscript, VariantCall, VariantExperiment, + VariantPrimaryKey, ) if TYPE_CHECKING: @@ -116,14 +117,7 @@ def add_variants( # database does not returt the ids of the created objects. So first create all # the variants and then create the variant calls. for variant_data in variants_data: - key = { - "species": variant_data["species"], - "genome_assembly": variant_data["genome_assembly"], - "chromosome": variant_data["chromosome"], - "position": variant_data["position"], - "reference": variant_data["reference"], - "alternative": variant_data["alternative"], - } + key = {key: variant_data[key] for key in VariantPrimaryKey} # To reduce the hits to the database use cache for variants. key_tuple = tuple(key.values()) if key_tuple not in variant_cache: @@ -136,8 +130,11 @@ def add_variants( data=data, sample=sample, quality=variant_data.get("quality"), + depth_norm_quality=variant_data.get("depth_norm_quality"), + unfiltered_allele_depth=variant_data.get("unfiltered_allele_depth"), depth=variant_data.get("depth"), genotype=variant_data.get("genotype"), + genotype_quality=variant_data.get("genotype_quality"), filter=variant_data.get("filter"), experiment=experiment, ) @@ -161,14 +158,7 @@ def add_variants_annotations( for annotation_data in typed_data: - key = { - "species": annotation_data["species"], - "genome_assembly": annotation_data["genome_assembly"], - "chromosome": annotation_data["chromosome"], - "position": annotation_data["position"], - "reference": annotation_data["reference"], - "alternative": annotation_data["alternative"], - } + key = {key: annotation_data[key] for key in VariantPrimaryKey} # To reduce the hits to the database use cache for variants. key_tuple = tuple(key.values()) if key_tuple not in variant_cache: diff --git a/resolwe_bio/variants/migrations/0001_initial.py b/resolwe_bio/variants/migrations/0001_initial.py index ebddc967d..f8afc33eb 100644 --- a/resolwe_bio/variants/migrations/0001_initial.py +++ b/resolwe_bio/variants/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.13 on 2024-05-16 11:48 +# Generated by Django 4.2.11 on 2024-05-21 11:58 from django.conf import settings import django.contrib.postgres.fields @@ -11,8 +11,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ("flow", "0026_create_unaccent_full_text_search_config"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("flow", "0024_update_sql_entity_methods"), ] operations = [ @@ -134,7 +134,7 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("quality", models.FloatField()), + ("quality", models.FloatField(blank=True, null=True)), ("depth_norm_quality", models.FloatField(blank=True, null=True)), ( "unfiltered_allele_depth", @@ -146,7 +146,7 @@ class Migration(migrations.Migration): "genotype_quality", models.PositiveIntegerField(blank=True, null=True), ), - ("filter", models.CharField(max_length=20)), + ("filter", models.CharField(blank=True, max_length=20, null=True)), ( "data", models.ForeignKey( @@ -218,7 +218,7 @@ class Migration(migrations.Migration): ( "variant_annotation", models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, + on_delete=django.db.models.deletion.CASCADE, related_name="transcripts", to="resolwe_bio_variants.variantannotation", ), diff --git a/resolwe_bio/variants/models.py b/resolwe_bio/variants/models.py index 697377a79..9c0ef3fea 100644 --- a/resolwe_bio/variants/models.py +++ b/resolwe_bio/variants/models.py @@ -13,7 +13,7 @@ from resolwe.auditlog.models import AuditModel from resolwe.flow.models import Data from resolwe.flow.models import Entity as Sample -from resolwe.permissions.models import PermissionGroup, PermissionInterface +from resolwe.permissions.models import PermissionInterface # TODO: sync with the kb, at least the first entry. SPECIES_MAX_LENGTH = 50 @@ -38,6 +38,15 @@ FEATURE_ID_MAX_LENGTH = 200 FILTER_MAX_LENGTH = 20 +VariantPrimaryKey = ( + "species", + "genome_assembly", + "chromosome", + "position", + "reference", + "alternative", +) + class Variant(AuditModel): """Describe a variant in the database.""" @@ -70,14 +79,7 @@ class Meta: constraints = [ models.UniqueConstraint( - fields=[ - "species", - "genome_assembly", - "chromosome", - "position", - "reference", - "alternative", - ], + fields=VariantPrimaryKey, name="uniq_composite_key_variants", ), ] @@ -177,7 +179,8 @@ class VariantExperiment(AuditModel): class VariantCall(AuditModel, PermissionInterface): """VariantCall object.""" - def permission_proxy(self) -> str: + @classmethod + def permission_proxy(cls) -> str: """Return the permission proxy name.""" return "sample" diff --git a/resolwe_bio/variants/serializers.py b/resolwe_bio/variants/serializers.py index 322f38277..80c82d6fd 100644 --- a/resolwe_bio/variants/serializers.py +++ b/resolwe_bio/variants/serializers.py @@ -8,11 +8,13 @@ from rest_framework import serializers +from resolwe.flow.serializers.fields import DictRelatedField from resolwe.rest.serializers import SelectiveFieldMixin from resolwe_bio.variants.models import ( Variant, VariantAnnotation, + VariantAnnotationTranscript, VariantCall, VariantExperiment, ) @@ -37,9 +39,36 @@ class Meta: ] +class VariantTranscriptSerializer(SelectiveFieldMixin, serializers.ModelSerializer): + """Serializer for VariantAnnotationTranscript objects.""" + + class Meta: + """Serializer configuration.""" + + model = VariantAnnotationTranscript + fields = [ + "id", + "variant_annotation_id", + "annotation", + "annotation_impact", + "gene", + "protein_impact", + "transcript_ids", + "canonical", + ] + + class VariantAnnotationSerializer(SelectiveFieldMixin, serializers.ModelSerializer): """Serializer for VariantAnnotation objects.""" + transcripts = DictRelatedField( + queryset=VariantAnnotationTranscript.objects.all(), + serializer=VariantTranscriptSerializer, + allow_null=True, + required=False, + many=True, + ) + class Meta: """Serializer configuration.""" @@ -48,17 +77,11 @@ class Meta: "id", "variant_id", "type", - "transcripts__annotation", - "transcripts__annotation_impact", - "transcripts__gene", - "transcripts__protein_impact", - "transcripts__transcript_ids", - "transcripts__canonical", "clinical_diagnosis", "clinical_significance", "dbsnp_id", "clinvar_id", - "data_id", + "transcripts", ] @@ -89,4 +112,4 @@ class Meta: """Serializer configuration.""" model = VariantExperiment - fields = ["id", "date", "contributor", "variant_data_source"] + fields = ["id", "timestamp", "contributor", "variant_data_source"] diff --git a/resolwe_bio/variants/tests/test_variant.py b/resolwe_bio/variants/tests/test_variant.py index 94f536476..7730e6714 100644 --- a/resolwe_bio/variants/tests/test_variant.py +++ b/resolwe_bio/variants/tests/test_variant.py @@ -1,17 +1,22 @@ +from typing import TYPE_CHECKING from unittest.mock import Mock from django.contrib.auth import get_user_model from django.test import TestCase from rest_framework import status -from rest_framework.test import APIRequestFactory +from rest_framework.test import APIRequestFactory, force_authenticate from resolwe.flow.executors.socket_utils import Message, MessageType -from resolwe.flow.models import Data, Entity, Process +from resolwe.flow.models import Data +from resolwe.flow.models import Entity as Sample +from resolwe.flow.models import Process +from resolwe.permissions.models import Permission -from resolwe_bio.variants.listener_plugin import VariantCommands +from resolwe_bio.variants.listener_plugin import VariantCommands, VariantData from resolwe_bio.variants.models import ( Variant, VariantAnnotation, + VariantAnnotationTranscript, VariantCall, VariantExperiment, ) @@ -26,16 +31,28 @@ VariantViewSet, ) +if TYPE_CHECKING: + from django.contrib.auth.models import User +else: + User = get_user_model() + class PrepareDataMixin: """Prepare the data for all variant tests.""" + variants: list[Variant] + annotations: list[VariantAnnotation] + experiments: list[VariantExperiment] + calls: list[VariantCall] + contributor: User + @classmethod def setUpTestData(cls): """Set up the test data.""" - cls.contributor = get_user_model().objects.get_or_create( + cls.contributor = User.objects.get_or_create( username="contributor", email="contributor@genialis.com" )[0] + sample = Sample.objects.create(contributor=cls.contributor) cls.variants = Variant.objects.bulk_create( [ Variant( @@ -60,19 +77,28 @@ def setUpTestData(cls): [ VariantAnnotation( variant=cls.variants[0], - type="annotation type 1", + type="SNP", + clinical_diagnosis="clinical diagnosis 1", + clinical_significance="clinical significance 1", + dbsnp_id="dbsnp_id 1", + clinvar_id="clinical_var_id 1", + ) + ] + ) + # Create the transcript data. + VariantAnnotationTranscript.objects.bulk_create( + [ + VariantAnnotationTranscript( + variant_annotation=cls.annotations[0], annotation="annotation 1", annotation_impact="impact 1", gene="gene 1", protein_impact="protein impact 1", - feature_id=["f1", "f2"], - clinical_diagnosis="clinical diagnosis 1", - clinical_significance="clinical significance 1", - dbsnp_id="dbsnp_id 1", - clinical_var_id="clinical_var_id 1", + transcript_ids=["f1", "f2"], ) ] ) + cls.experiments = VariantExperiment.objects.bulk_create( [ VariantExperiment( @@ -88,19 +114,27 @@ def setUpTestData(cls): cls.calls = VariantCall.objects.bulk_create( [ VariantCall( + sample=sample, variant=cls.variants[0], quality=0.7, + depth_norm_quality=0.7, + unfiltered_allele_depth=1, depth=15, filter="filter 1", - genotype="genotype 1", + genotype="1", + genotype_quality=1, experiment=cls.experiments[0], ), VariantCall( + sample=sample, variant=cls.variants[1], quality=0.2, + depth_norm_quality=0.2, + unfiltered_allele_depth=2, depth=5, filter="filter 2", - genotype="genotype 2", + genotype="2", + genotype_quality=2, experiment=cls.experiments[1], ), ] @@ -111,12 +145,12 @@ class ListenerPluginTest(TestCase): def setUp(self): """Prepare the test data.""" - contributor = get_user_model().objects.get_or_create( + contributor = User.objects.get_or_create( username="contributor", email="contributor@genialis.com" )[0] self.data = Data.objects.create( contributor=contributor, - entity=Entity.objects.create(contributor=contributor), + entity=Sample.objects.create(contributor=contributor), process=Process.objects.create(contributor=contributor), status=Data.STATUS_PROCESSING, ) @@ -135,35 +169,43 @@ def test_add_variants(self): # The first variant already exists in the database and should be re-used. # The second one is new and should be created. - metadata = { - "species": "Homo Sapiens", - "genome_assembly": "ENSEMBL", - "variant_data_source": "process", - } - variants_data = [ + data_source = "process" + + variants_data: list[VariantData] = [ { + # Identity the variant. + "species": "Homo Sapiens", + "genome_assembly": "ENSEMBL", "chromosome": "chr1", "position": 1, "reference": "ref1", "alternative": "alt1", + # Variant annotation data. "quality": 1, "depth": 1, "genotype": "1", "filter": "1", }, { + # Variant data. + "species": "Homo Sapiens", + "genome_assembly": "ENSEMBL", "chromosome": "chr2", "position": 2, "reference": "ref2", "alternative": "alt2", + # Variant annotation data. "quality": 2, + "depth_norm_quality": 0.2, + "unfiltered_allele_depth": 2, "depth": 2, "genotype": "2", + "genotype_quality": 2, "filter": "2", }, ] message = Message( - MessageType.COMMAND, "variants_test", [metadata, variants_data] + MessageType.COMMAND, "variants_test", [data_source, variants_data] ) manager_mock = Mock(data=Mock(return_value=self.data)) VariantCommands().add_variants( @@ -189,6 +231,7 @@ def test_add_variants(self): "alternative": "alt2", }, ] + self.assertCountEqual( Variant.objects.all().values( "species", @@ -210,9 +253,12 @@ def test_add_variants(self): "variant_id": 1, "experiment_id": experiment.pk, "quality": 1.0, + "depth_norm_quality": None, + "unfiltered_allele_depth": None, "depth": 1, "filter": "1", "genotype": "1", + "genotype_quality": None, "data_id": self.data.pk, }, { @@ -220,9 +266,12 @@ def test_add_variants(self): "variant_id": 2, "experiment_id": experiment.pk, "quality": 2.0, + "depth_norm_quality": 0.2, + "unfiltered_allele_depth": 2, "depth": 2, "filter": "2", "genotype": "2", + "genotype_quality": 2, "data_id": self.data.pk, }, ] @@ -232,9 +281,12 @@ def test_add_variants(self): "variant_id", "experiment_id", "quality", + "depth_norm_quality", + "unfiltered_allele_depth", "depth", "filter", "genotype", + "genotype_quality", "data_id", ), expected_calls, @@ -315,7 +367,7 @@ def test_filter(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) request = APIRequestFactory().get( - "/variant", {"annotation__type__contains": "type 1"} + "/variant", {"annotation__type__contains": "SN"} ) expected = VariantSerializer(self.variants[:1], many=True).data response = self.view(request) @@ -324,14 +376,15 @@ def test_filter(self): # Filter by annotation. request = APIRequestFactory().get( - "/variant", {"annotation__annotation": "annotation 1"} + "/variant", {"annotation__transcripts__annotation": "annotation 1"} ) expected = VariantSerializer(self.variants[:1], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) request = APIRequestFactory().get( - "/variant", {"annotation__annotation__isnull": True} + "/variant", {"annotation__transcripts__annotation__isnull": True} ) expected = VariantSerializer(self.variants[1:], many=True).data response = self.view(request) @@ -340,14 +393,16 @@ def test_filter(self): # Filter by annotation impact. request = APIRequestFactory().get( - "/variant", {"annotation__annotation_impact__icontains": "MpAcT 1"} + "/variant", + {"annotation__transcripts__annotation_impact__icontains": "MpAcT 1"}, ) expected = VariantSerializer(self.variants[:1], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) request = APIRequestFactory().get( - "/variant", {"annotation__annotation_impact__isnull": True} + "/variant", {"annotation__transcripts__annotation_impact__isnull": True} ) expected = VariantSerializer(self.variants[1:], many=True).data response = self.view(request) @@ -355,29 +410,33 @@ def test_filter(self): self.assertCountEqual(response.data, expected) # Filter by gene. - request = APIRequestFactory().get("/variant", {"annotation__gene": "gene 1"}) + request = APIRequestFactory().get( + "/variant", {"annotation__transcripts__gene": "gene 1"} + ) expected = VariantSerializer(self.variants[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) request = APIRequestFactory().get( - "/variant", {"annotation__gene__isnull": True} + "/variant", {"annotation__transcripts__gene__isnull": True} ) expected = VariantSerializer(self.variants[1:], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) # Filter by protein impact. request = APIRequestFactory().get( - "/variant", {"annotation__protein_impact__icontains": "protein"} + "/variant", + {"annotation__transcripts__protein_impact__icontains": "protein"}, ) expected = VariantSerializer(self.variants[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) request = APIRequestFactory().get( - "/variant", {"annotation__protein_impact__isnull": True} + "/variant", {"annotation__transcripts__protein_impact__isnull": True} ) expected = VariantSerializer(self.variants[1:], many=True).data response = self.view(request) @@ -505,18 +564,21 @@ def setUp(self) -> None: self.annotations.append( VariantAnnotation.objects.create( variant=self.variants[1], - type="annotation type 2", - annotation="annotation 2", - annotation_impact="impact 2", - gene="gene 2", - protein_impact="protein impact 2", - feature_id=["f1", "f2"], + type="INDEL", clinical_diagnosis="clinical diagnosis 2", clinical_significance="clinical significance 2", dbsnp_id="dbsnp_id 2", - clinical_var_id="clinical_var_id 2", + clinvar_id="clinical_var_id 2", ) ) + VariantAnnotationTranscript.objects.create( + variant_annotation=self.annotations[-1], + annotation="annotation 2", + annotation_impact="impact 2", + gene="gene 2", + protein_impact="protein impact 2", + transcript_ids=["f1", "f2"], + ) return super().setUp() def test_filter(self): @@ -537,42 +599,46 @@ def test_filter(self): self.assertCountEqual(response.data, expected) # Filter by type. - request = APIRequestFactory().get( - "/variantannotation", {"type": "annotation type 1"} - ) + request = APIRequestFactory().get("/variantannotation", {"type": "SNP"}) expected = VariantAnnotationSerializer(self.annotations[:1], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) # Filter by annotation. request = APIRequestFactory().get( - "/variantannotation", {"annotation": "annotation 1"} + "/variantannotation", {"transcripts__annotation": "annotation 1"} ) expected = VariantAnnotationSerializer(self.annotations[:1], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) # Filter by annotation impact. request = APIRequestFactory().get( - "/variantannotation", {"annotation_impact": "impact 1"} + "/variantannotation", {"transcripts__annotation_impact": "impact 1"} ) expected = VariantAnnotationSerializer(self.annotations[:1], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) # Filter by gene. - request = APIRequestFactory().get("/variantannotation", {"gene": "gene 1"}) + request = APIRequestFactory().get( + "/variantannotation", {"transcripts__gene": "gene 1"} + ) expected = VariantAnnotationSerializer(self.annotations[:1], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) # Filter by protein impact. request = APIRequestFactory().get( - "/variantannotation", {"protein_impact": "protein impact 1"} + "/variantannotation", {"transcripts__protein_impact": "protein impact 1"} ) expected = VariantAnnotationSerializer(self.annotations[:1], many=True).data response = self.view(request) @@ -608,21 +674,26 @@ def test_filter(self): # Filter by clinical_var_id. request = APIRequestFactory().get( - "/variantannotation", {"clinical_var_id": "clinical_var_id 1"} + "/variantannotation", {"clinvar_id": "clinical_var_id 1"} ) expected = VariantAnnotationSerializer(self.annotations[:1], many=True).data response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) def test_ordering(self): # Order by gene. - request = APIRequestFactory().get("/variantannotation", {"ordering": "gene"}) + request = APIRequestFactory().get( + "/variantannotation", {"ordering": "transcripts__gene"} + ) expected = VariantAnnotationSerializer(self.annotations, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) - request = APIRequestFactory().get("/variantannotation", {"ordering": "-gene"}) + request = APIRequestFactory().get( + "/variantannotation", {"ordering": "-transcripts__gene"} + ) expected = VariantAnnotationSerializer(self.annotations[::-1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -630,14 +701,14 @@ def test_ordering(self): # Order by protein impact. request = APIRequestFactory().get( - "/variantannotation", {"ordering": "protein_impact"} + "/variantannotation", {"ordering": "transcripts__protein_impact"} ) expected = VariantAnnotationSerializer(self.annotations, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) request = APIRequestFactory().get( - "/variantannotation", {"ordering": "-protein_impact"} + "/variantannotation", {"ordering": "-transcripts__protein_impact"} ) expected = VariantAnnotationSerializer(self.annotations[::-1], many=True).data response = self.view(request) @@ -646,14 +717,14 @@ def test_ordering(self): # Sort by annotation. request = APIRequestFactory().get( - "/variantannotation", {"ordering": "annotation"} + "/variantannotation", {"ordering": "transcripts__annotation"} ) expected = VariantAnnotationSerializer(self.annotations, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) request = APIRequestFactory().get( - "/variantannotation", {"ordering": "-annotation"} + "/variantannotation", {"ordering": "-transcripts__annotation"} ) expected = VariantAnnotationSerializer(self.annotations[::-1], many=True).data response = self.view(request) @@ -683,8 +754,24 @@ def setUp(self) -> None: return super().setUp() def test_filter(self): - # No filter. + # No filter no permission for public. + request = APIRequestFactory().get("/variantcall") + response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertCountEqual(response.data, []) + + # No filter no permissions for contributor. request = APIRequestFactory().get("/variantcall") + force_authenticate(request, self.contributor) + response = self.view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertCountEqual(response.data, []) + + # No filter read permissions for contributor. + for call in self.calls: + call.sample.set_permission(Permission.VIEW, self.contributor) + request = APIRequestFactory().get("/variantcall") + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -692,6 +779,7 @@ def test_filter(self): # Filter by id. request = APIRequestFactory().get("/variantcall", {"id": self.calls[0].id}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -699,6 +787,7 @@ def test_filter(self): # Filter by quality. request = APIRequestFactory().get("/variantcall", {"quality__gt": 0.5}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -706,6 +795,7 @@ def test_filter(self): # Filter by depth. request = APIRequestFactory().get("/variantcall", {"depth__gt": 10}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -715,6 +805,7 @@ def test_filter(self): request = APIRequestFactory().get( "/variantcall", {"variant__id": self.variants[0].id} ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -724,6 +815,7 @@ def test_filter(self): request = APIRequestFactory().get( "/variantcall", {"variant__species": self.variants[0].species} ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -734,6 +826,7 @@ def test_filter(self): "/variantcall", {"variant__genome_assembly": self.variants[0].genome_assembly}, ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -743,6 +836,7 @@ def test_filter(self): request = APIRequestFactory().get( "/variantcall", {"variant__chromosome": self.variants[0].chromosome} ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -756,6 +850,7 @@ def test_filter(self): "variant__position__lt": self.variants[1].position, }, ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -765,6 +860,7 @@ def test_filter(self): request = APIRequestFactory().get( "/variantcall", {"variant__reference": self.variants[0].reference} ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -774,16 +870,7 @@ def test_filter(self): request = APIRequestFactory().get( "/variantcall", {"variant__alternative": self.variants[0].alternative} ) - expected = VariantCallSerializer(self.calls[:1], many=True).data - response = self.view(request) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertCountEqual(response.data, expected) - - # Filter by variant annotation. - request = APIRequestFactory().get( - "/variantcall", - {"variant__annotation__annotation": self.annotations[0].annotation}, - ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -793,19 +880,26 @@ def test_filter(self): request = APIRequestFactory().get( "/variantcall", {"experiment__id": self.experiments[0].id} ) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[:1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertCountEqual(response.data, expected) def test_ordering(self): + # Set permissions for the contributor. + for call in self.calls: + call.sample.set_permission(Permission.VIEW, self.contributor) + # Order by id. request = APIRequestFactory().get("/variantcall", {"ordering": "id"}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) request = APIRequestFactory().get("/variantcall", {"ordering": "-id"}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[::-1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -813,11 +907,13 @@ def test_ordering(self): # Order by quality. request = APIRequestFactory().get("/variantcall", {"ordering": "-quality"}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) request = APIRequestFactory().get("/variantcall", {"ordering": "quality"}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[::-1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -825,11 +921,13 @@ def test_ordering(self): # Order by depth. request = APIRequestFactory().get("/variantcall", {"ordering": "-depth"}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) request = APIRequestFactory().get("/variantcall", {"ordering": "depth"}) + force_authenticate(request, self.contributor) expected = VariantCallSerializer(self.calls[::-1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -868,7 +966,7 @@ def test_filter(self): self.assertCountEqual(response.data, expected) # Filter by contributor. - contributor2 = get_user_model().objects.create( + contributor2 = User.objects.create( username="contributor2", email="contributor2@genialis.com" ) self.experiments[1].contributor = contributor2 @@ -897,7 +995,7 @@ def test_filter(self): # Filter by date. request = APIRequestFactory().get( - "/variantexperiment", {"date__lt": self.experiments[1].date} + "/variantexperiment", {"timestamp__lt": self.experiments[1].timestamp} ) expected = VariantExperimentSerializer(self.experiments[:1], many=True).data response = self.view(request) @@ -918,19 +1016,23 @@ def test_ordering(self): self.assertEqual(response.data, expected) # Order by date. - request = APIRequestFactory().get("/variantexperiment", {"ordering": "date"}) + request = APIRequestFactory().get( + "/variantexperiment", {"ordering": "timestamp"} + ) expected = VariantExperimentSerializer(self.experiments, many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) - request = APIRequestFactory().get("/variantexperiment", {"ordering": "-date"}) + request = APIRequestFactory().get( + "/variantexperiment", {"ordering": "-timestamp"} + ) expected = VariantExperimentSerializer(self.experiments[::-1], many=True).data response = self.view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, expected) # Order by contributor. - contributor2 = get_user_model().objects.create( + contributor2 = User.objects.create( username="contributor2", email="contributor2@genialis.com" ) self.experiments[1].contributor = contributor2 diff --git a/resolwe_bio/variants/views.py b/resolwe_bio/variants/views.py index ffd553a24..68cbf7dd3 100644 --- a/resolwe_bio/variants/views.py +++ b/resolwe_bio/variants/views.py @@ -50,15 +50,22 @@ class VariantAnnotationViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): filter_backends = [filters.rest_framework.DjangoFilterBackend, OrderingFilter] filterset_class = VariantAnnotationFilter - ordering_fields = ("gene", "protein_impact", "annotation", "clinical_significance") + ordering_fields = ( + "transcripts__gene", + "transcripts__protein_impact", + "transcripts__annotation", + "clinical_significance", + ) class VariantCallViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): - """VariantCall endpoint.""" + """VariantCall endpoint. + + The default filter backends are used so permissions are respected. + """ queryset = VariantCall.objects.all() serializer_class = VariantCallSerializer - filter_backends = [filters.rest_framework.DjangoFilterBackend, OrderingFilter] filterset_class = VariantCallFilter ordering_fields = ("id", "quality", "depth") @@ -72,4 +79,4 @@ class VariantExperimentViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): filter_backends = [filters.rest_framework.DjangoFilterBackend, OrderingFilter] filterset_class = VariantExperimentFilter - ordering_fields = ("id", "date", "contributor__email") + ordering_fields = ("id", "timestamp", "contributor__email")