-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adding cases_analysis module (#1735)
- Loading branch information
Showing
20 changed files
with
645 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from django.contrib import admin | ||
|
||
from .models import CaseAnalysis, CaseAnalysisSession | ||
|
||
# Register your models here. | ||
admin.site.register(CaseAnalysis) | ||
admin.site.register(CaseAnalysisSession) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class CasesAnalysisConfig(AppConfig): | ||
name = "cases_analysis" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Generated by Django 3.2.25 on 2024-06-24 11:33 | ||
|
||
import uuid | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
("variants", "0105_auto_20240529_1403"), | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="CaseAnalysis", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||
), | ||
), | ||
("sodar_uuid", models.UUIDField(default=uuid.uuid4, unique=True)), | ||
("date_created", models.DateTimeField(auto_now_add=True)), | ||
("date_modified", models.DateTimeField(auto_now=True)), | ||
( | ||
"case", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, to="variants.case", unique=True | ||
), | ||
), | ||
], | ||
options={ | ||
"abstract": False, | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name="CaseAnalysisSession", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||
), | ||
), | ||
("sodar_uuid", models.UUIDField(default=uuid.uuid4, unique=True)), | ||
("date_created", models.DateTimeField(auto_now_add=True)), | ||
("date_modified", models.DateTimeField(auto_now=True)), | ||
( | ||
"caseanalysis", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="cases_analysis.caseanalysis", | ||
), | ||
), | ||
( | ||
"user", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL | ||
), | ||
), | ||
], | ||
options={ | ||
"unique_together": {("caseanalysis", "user")}, | ||
}, | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
"""Models for the ``cases_analysis`` module.""" | ||
|
||
import uuid as uuid_object | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.db import models | ||
from django.urls import reverse | ||
|
||
from variants.models import Case | ||
|
||
#: User model. | ||
User = get_user_model() | ||
|
||
|
||
class BaseModel(models.Model): | ||
"""Base model with sodar_uuid and creation/update time.""" | ||
|
||
#: UUID used in URLs. | ||
sodar_uuid = models.UUIDField(default=uuid_object.uuid4, unique=True) | ||
#: DateTime of creation | ||
date_created = models.DateTimeField(auto_now_add=True) | ||
#: DateTime of last modification | ||
date_modified = models.DateTimeField(auto_now=True) | ||
|
||
class Meta: | ||
abstract = True | ||
|
||
|
||
class CaseAnalysis(BaseModel): | ||
"""Analysis of a case (at most once for now).""" | ||
|
||
#: The related case. | ||
case = models.ForeignKey( | ||
Case, | ||
on_delete=models.CASCADE, | ||
unique=True, # for now | ||
) | ||
|
||
def __str__(self): | ||
return f"CaseAnalysis '{self.sodar_uuid}'" | ||
|
||
def get_absolute_url(self): | ||
return reverse( | ||
"cases_analysis:api-caseanalysis-detail", | ||
kwargs={"case": self.case.sodar_uuid, "caseanalysis": self.sodar_uuid}, | ||
) | ||
|
||
|
||
class CaseAnalysisSession(BaseModel): | ||
"""A user session for a ``CaseAnalysis`` (at most one per user for now).""" | ||
|
||
#: The related ``CaseAnalysis``. | ||
caseanalysis = models.ForeignKey( | ||
CaseAnalysis, | ||
on_delete=models.CASCADE, | ||
) | ||
#: The related ``User``. | ||
user = models.ForeignKey( | ||
User, | ||
on_delete=models.CASCADE, | ||
) | ||
|
||
def __str__(self): | ||
return f"CaseAnalysisSession '{self.sodar_uuid}'" | ||
|
||
def get_absolute_url(self): | ||
return reverse( | ||
"cases_analysis:api-caseanalysissession-detail", | ||
kwargs={ | ||
"case": self.caseanalysis.case.sodar_uuid, | ||
"caseanalysissession": self.sodar_uuid, | ||
}, | ||
) | ||
|
||
class Meta: | ||
# We constrain to one session per case analysis and user for now. | ||
unique_together = [("caseanalysis", "user")] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from projectroles import rules as pr_rules | ||
import rules | ||
|
||
rules.add_perm( | ||
"cases_analysis.view_data", | ||
rules.is_superuser | ||
| pr_rules.is_project_owner | ||
| pr_rules.is_project_delegate | ||
| pr_rules.is_project_contributor | ||
| pr_rules.is_project_guest, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from rest_framework import serializers | ||
|
||
from cases_analysis.models import CaseAnalysis, CaseAnalysisSession | ||
|
||
|
||
class BaseSerializer(serializers.ModelSerializer): | ||
"""Base serializer for models with sodar_uuid and creation/update time.""" | ||
|
||
class Meta: | ||
fields = [ | ||
"sodar_uuid", | ||
"date_created", | ||
"date_modified", | ||
] | ||
|
||
|
||
class CaseAnalysisSerializer(BaseSerializer): | ||
"""Serializer for ``CaseAnalysis``.""" | ||
|
||
#: Serialize ``case`` as its ``sodar_uuid``. | ||
case = serializers.ReadOnlyField(source="case.sodar_uuid") | ||
|
||
class Meta: | ||
model = CaseAnalysis | ||
fields = BaseSerializer.Meta.fields + ["case"] | ||
read_only_fields = fields | ||
|
||
|
||
class CaseAnalysisSessionSerializer(BaseSerializer): | ||
"""Serializer for ``CaseAnalysisSession``.""" | ||
|
||
#: Serialize ``caseanalysis`` as its ``sodar_uuid``. | ||
caseanalysis = serializers.ReadOnlyField(source="caseanalysis.sodar_uuid") | ||
#: Serialize ``case`` as its ``sodar_uuid`` (via ``caseanalysis``) | ||
case = serializers.ReadOnlyField(source="caseanalysis.case.sodar_uuid") | ||
#: Serialize ``user`` as its ``sodar_uuid``. | ||
user = serializers.ReadOnlyField(source="user.sodar_uuid") | ||
|
||
class Meta: | ||
model = CaseAnalysisSession | ||
fields = BaseSerializer.Meta.fields + ["caseanalysis", "case", "user"] | ||
read_only_fields = fields |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import datetime | ||
|
||
import factory | ||
|
||
from cases_analysis.models import CaseAnalysis, CaseAnalysisSession | ||
from varfish.users.tests.factories import UserFactory | ||
from variants.tests.factories import CaseFactory | ||
|
||
|
||
class BaseFactory(factory.django.DjangoModelFactory): | ||
sodar_uuid = factory.Faker("uuid4") | ||
date_created = factory.LazyFunction(datetime.datetime.now) | ||
date_modified = factory.LazyFunction(datetime.datetime.now) | ||
|
||
class Meta: | ||
abstract = True | ||
|
||
|
||
class CaseAnalysisFactory(BaseFactory): | ||
case = factory.SubFactory(CaseFactory) | ||
|
||
class Meta: | ||
model = CaseAnalysis | ||
abstract = False | ||
|
||
|
||
class CaseAnalysisSessionFactory(BaseFactory): | ||
user = factory.SubFactory(UserFactory) | ||
caseanalysis = factory.SubFactory(CaseAnalysisFactory) | ||
|
||
class Meta: | ||
model = CaseAnalysisSession | ||
abstract = False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
"""Test models and factories""" | ||
|
||
from freezegun import freeze_time | ||
from test_plus.test import TestCase | ||
|
||
from cases_analysis.models import CaseAnalysis, CaseAnalysisSession | ||
from cases_analysis.tests.factories import CaseAnalysisFactory, CaseAnalysisSessionFactory | ||
|
||
|
||
@freeze_time("2012-01-14 12:00:01") | ||
class TestCaseAnalysis(TestCase): | ||
def test_create(self): | ||
self.assertEqual(CaseAnalysis.objects.count(), 0) | ||
CaseAnalysisFactory() | ||
self.assertEqual(CaseAnalysis.objects.count(), 1) | ||
|
||
def test_get_absolute_url(self): | ||
caseanalysis = CaseAnalysisFactory() | ||
self.assertEqual( | ||
( | ||
f"/cases-analysis/api/caseanalysis/{caseanalysis.case.sodar_uuid}/" | ||
f"{caseanalysis.sodar_uuid}/" | ||
), | ||
caseanalysis.get_absolute_url(), | ||
) | ||
|
||
def test_str(self): | ||
caseanalysis = CaseAnalysisFactory() | ||
self.assertEqual( | ||
f"CaseAnalysis '{caseanalysis.sodar_uuid}'", | ||
caseanalysis.__str__(), | ||
) | ||
|
||
|
||
@freeze_time("2012-01-14 12:00:01") | ||
class TestCaseAnalysisSession(TestCase): | ||
def test_create(self): | ||
self.assertEqual(CaseAnalysisSession.objects.count(), 0) | ||
CaseAnalysisSessionFactory() | ||
self.assertEqual(CaseAnalysisSession.objects.count(), 1) | ||
|
||
def test_get_absolute_url(self): | ||
caseanalysissession = CaseAnalysisSessionFactory() | ||
caseanalysis = caseanalysissession.caseanalysis | ||
self.assertEqual( | ||
( | ||
f"/cases-analysis/api/caseanalysissession/{caseanalysis.case.sodar_uuid}/" | ||
f"{caseanalysissession.sodar_uuid}/" | ||
), | ||
caseanalysissession.get_absolute_url(), | ||
) | ||
|
||
def test_str(self): | ||
caseanalysissession = CaseAnalysisSessionFactory() | ||
self.assertEqual( | ||
f"CaseAnalysisSession '{caseanalysissession.sodar_uuid}'", | ||
caseanalysissession.__str__(), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import json | ||
|
||
from django.urls import reverse | ||
from projectroles.tests.test_permissions_api import TestProjectAPIPermissionBase | ||
|
||
from cases_analysis.tests.factories import CaseAnalysisFactory, CaseAnalysisSessionFactory | ||
from variants.models import Case, CaseComments, CasePhenotypeTerms | ||
from variants.tests.factories import CaseCommentsFactory, CaseFactory, CasePhenotypeTermsFactory | ||
|
||
|
||
class TestCaseApiView(TestProjectAPIPermissionBase): | ||
"""Permission tests for the API views dealing with ``Case``.""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.case = CaseFactory(project=self.project) | ||
|
||
def test_list(self): | ||
url = reverse( | ||
"cases_analysis:api-caseanalysis-list", | ||
kwargs={"case": self.case.sodar_uuid}, | ||
) | ||
good_users = [ | ||
self.superuser, | ||
self.user_owner, | ||
self.user_delegate, | ||
self.user_contributor, | ||
self.user_guest, | ||
] | ||
bad_users_401 = [self.anonymous] | ||
bad_users_403 = [self.user_no_roles, self.user_finder_cat] | ||
self.assert_response(url, good_users, 200, method="GET") | ||
self.assert_response(url, bad_users_401, 401, method="GET") | ||
self.assert_response(url, bad_users_403, 403, method="GET") | ||
|
||
def test_retrieve(self): | ||
caseanalysis = CaseAnalysisFactory(case=self.case) | ||
url = reverse( | ||
"cases_analysis:api-caseanalysis-detail", | ||
kwargs={"case": self.case.sodar_uuid, "caseanalysis": caseanalysis.sodar_uuid}, | ||
) | ||
good_users = [ | ||
self.superuser, | ||
self.user_owner, | ||
self.user_delegate, | ||
self.user_contributor, | ||
self.user_guest, | ||
] | ||
bad_users_401 = [ | ||
self.anonymous, | ||
] | ||
bad_users_403 = [self.user_no_roles, self.user_finder_cat] | ||
self.assert_response(url, good_users, 200, method="GET") | ||
self.assert_response(url, bad_users_401, 401, method="GET") | ||
self.assert_response(url, bad_users_403, 403, method="GET") |
Oops, something went wrong.