Skip to content

Commit

Permalink
feat: adding cases_analysis module (#1735)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe committed Jun 24, 2024
1 parent 9561fb4 commit 21294ff
Show file tree
Hide file tree
Showing 20 changed files with 645 additions and 0 deletions.
Empty file.
7 changes: 7 additions & 0 deletions backend/cases_analysis/admin.py
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)
5 changes: 5 additions & 0 deletions backend/cases_analysis/apps.py
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"
73 changes: 73 additions & 0 deletions backend/cases_analysis/migrations/0001_initial.py
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.
77 changes: 77 additions & 0 deletions backend/cases_analysis/models.py
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")]
11 changes: 11 additions & 0 deletions backend/cases_analysis/rules.py
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,
)
42 changes: 42 additions & 0 deletions backend/cases_analysis/serializers.py
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.
33 changes: 33 additions & 0 deletions backend/cases_analysis/tests/factories.py
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
58 changes: 58 additions & 0 deletions backend/cases_analysis/tests/test_models.py
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__(),
)
55 changes: 55 additions & 0 deletions backend/cases_analysis/tests/test_permissions_api.py
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")
Loading

0 comments on commit 21294ff

Please sign in to comment.