Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions backend/fms_core/migrations/0082_v5_9_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.contrib.auth import get_user_model
from django.db import migrations, models
import django.db.models.deletion
import django.core.validators


ADMIN_USERNAME = 'biobankadmin'
Expand Down Expand Up @@ -39,6 +40,9 @@ def populate_parent_project(apps, schema_editor):
parent_project_name = project_obj.external_name or project_obj.name # use project external name in priority and defaults to the project name
parent_project_obj = ParentProject.objects.create(external_id=project_obj.external_id,
name=parent_project_name,
principal_investigator=project_obj.principal_investigator,
requestor_name=project_obj.requestor_name,
requestor_email=project_obj.requestor_email,
created_by_id=admin_user.id,
updated_by_id=admin_user.id)
reversion.add_to_revision(parent_project_obj)
Expand Down Expand Up @@ -76,6 +80,9 @@ class Migration(migrations.Migration):
('deleted', models.BooleanField(default=False, help_text='Whether this instance has been deleted.')),
('external_id', models.CharField(help_text='Identifier to connect to an external system.', max_length=200, unique=True)),
('name', models.CharField(help_text='Parent project name used by external client.', max_length=200)),
('principal_investigator', models.CharField(blank=True, help_text='The principal investigator of the project.', max_length=200, validators=[django.core.validators.EmailValidator()])),
('requestor_name', models.CharField(blank=True, help_text='The name of the requestor of the project.', max_length=200)),
('requestor_email', models.CharField(blank=True, help_text='The email of the requestor of the project.', max_length=200)),
('created_by', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='%(app_label)s_%(class)s_creation', to=settings.AUTH_USER_MODEL)),
('updated_by', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='%(app_label)s_%(class)s_modification', to=settings.AUTH_USER_MODEL)),
],
Expand Down Expand Up @@ -103,6 +110,18 @@ class Migration(migrations.Migration):
model_name='project',
name='external_name',
),
migrations.RemoveField(
model_name='project',
name='principal_investigator',
),
migrations.RemoveField(
model_name='project',
name='requestor_email',
),
migrations.RemoveField(
model_name='project',
name='requestor_name',
),
migrations.AlterField(
model_name='container',
name='kind',
Expand Down
5 changes: 4 additions & 1 deletion backend/fms_core/models/parent_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.db import models

from .tracked_model import TrackedModel

from ._validators import email_validator
from ._utils import add_error as _add_error

__all__ = ["ParentProject"]
Expand All @@ -13,6 +13,9 @@
class ParentProject(TrackedModel):
external_id = models.CharField(max_length=200, unique=True, help_text="Identifier to connect to an external system.")
name = models.CharField(max_length=200, help_text="Parent project name used by external client.")
principal_investigator = models.CharField(blank=True, max_length=200, help_text="The principal investigator of the project.")
requestor_name = models.CharField(blank=True, max_length=200, help_text="The name of the requestor of the project.")
requestor_email = models.CharField(blank=True, max_length=200, validators=[email_validator], help_text="The email of the requestor of the project.")

class Meta:
indexes = [
Expand Down
14 changes: 3 additions & 11 deletions backend/fms_core/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,16 @@

from ._constants import STANDARD_NAME_FIELD_LENGTH, PROJECT_STATUS_CHOICES
from ._utils import add_error as _add_error
from ._validators import name_validator, email_validator
from ._validators import name_validator

__all__ = ["Project"]

@reversion.register()
class Project(TrackedModel):
name = models.CharField(unique=True, max_length=STANDARD_NAME_FIELD_LENGTH, validators=[name_validator],
help_text="The name of the project.")
principal_investigator = models.CharField(blank=True, max_length=200, help_text="The principal investigator of the project.")
requestor_name = models.CharField(blank=True, max_length=200, help_text="The name of the requestor of the project.")
requestor_email = models.CharField(blank=True, max_length=200, validators=[email_validator],
help_text="The email of the requestor of the project.")
name = models.CharField(unique=True, max_length=STANDARD_NAME_FIELD_LENGTH, validators=[name_validator], help_text="The name of the project.")
targeted_end_date = models.DateField(blank=True, null=True, help_text="Targeted date to conclude the project.")
status = models.CharField(choices=((type, type) for type in PROJECT_STATUS_CHOICES), max_length=20, default="Open",
help_text="The status of the project.")

status = models.CharField(choices=((type, type) for type in PROJECT_STATUS_CHOICES), max_length=20, default="Open", help_text="The status of the project.")
parent_project = models.ForeignKey(ParentProject, blank=True, null=True, on_delete=models.PROTECT, related_name="projects", help_text="Parent project from external system.")

comment = models.TextField(blank=True, help_text="Other relevant information about the project.")

class Meta:
Expand Down
6 changes: 6 additions & 0 deletions backend/fms_core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,12 +599,18 @@ class Meta:
class ProjectSerializer(serializers.ModelSerializer):
external_id = serializers.CharField(read_only=True, source="parent_project.external_id")
external_name = serializers.CharField(read_only=True, source="parent_project.name")
principal_investigator = serializers.CharField(read_only=True, source="parent_project.principal_investigator")
requestor_name = serializers.CharField(read_only=True, source="parent_project.requestor_name")
requestor_email = serializers.CharField(read_only=True, source="parent_project.requestor_email")
class Meta:
model = Project
fields = '__all__'


class ProjectExportSerializer(serializers.ModelSerializer):
principal_investigator = serializers.CharField(read_only=True, source="parent_project.principal_investigator")
requestor_name = serializers.CharField(read_only=True, source="parent_project.requestor_name")
requestor_email = serializers.CharField(read_only=True, source="parent_project.requestor_email")
class Meta:
model = Project
fields = ("id", "name", "principal_investigator", "requestor_name", "requestor_email", "status", "targeted_end_date", "comment")
Expand Down
10 changes: 6 additions & 4 deletions backend/fms_core/services/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,8 @@ def create_validation_info_file(dataset_obj: Dataset, validator_obj: User, is_va
return None, errors, warnings

dataset_id = str(dataset_obj.id)
project_requestor_email = dataset_obj.project.requestor_email
project_principal_investigator = dataset_obj.project.parent_project and dataset_obj.project.parent_project.principal_investigator
project_requestor_email = dataset_obj.project.parent_project and dataset_obj.project.parent_project.requestor_email
lane = str(dataset_obj.lane)

filename, timestamp = make_timestamped_filename(file_prefix[is_validation_revocation] + "_" + external_project_id + "_" + dataset_id + "_" + lane + ".json")
Expand All @@ -411,7 +412,7 @@ def create_validation_info_file(dataset_obj: Dataset, validator_obj: User, is_va
"timestamp": timestamp,
"external_project_id": external_project_id,
"project_name": dataset_obj.project.name,
"project_principal_investigator": dataset_obj.project.principal_investigator,
"project_principal_investigator": project_principal_investigator,
"project_requestor_email": project_requestor_email,
"run_id": dataset_obj.experiment_run.id,
"run_name": dataset_obj.experiment_run.name,
Expand Down Expand Up @@ -471,7 +472,8 @@ def create_release_info_file(dataset_obj: Dataset, readsets_obj: List[Readset],
return None, errors, warnings

dataset_id = str(dataset_obj.id)
project_requestor_email = dataset_obj.project.requestor_email
project_principal_investigator = dataset_obj.project.parent_project and dataset_obj.project.parent_project.principal_investigator
project_requestor_email = dataset_obj.project.parent_project and dataset_obj.project.parent_project.requestor_email
lane = str(dataset_obj.lane)

filename, timestamp = make_timestamped_filename(file_prefix[is_release_revocation] + "_" + external_project_id + "_" + dataset_id + "_" + lane + ".json")
Expand All @@ -480,7 +482,7 @@ def create_release_info_file(dataset_obj: Dataset, readsets_obj: List[Readset],
"timestamp": timestamp,
"external_project_id": external_project_id,
"project_name": dataset_obj.project.name,
"project_principal_investigator": dataset_obj.project.principal_investigator,
"project_principal_investigator": project_principal_investigator,
"project_requestor_email": project_requestor_email,
"run_id": dataset_obj.experiment_run.id,
"run_name": dataset_obj.experiment_run.name,
Expand Down
25 changes: 14 additions & 11 deletions backend/fms_core/services/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,19 @@ def create_full_project(name=None, principal_investigator=None, requestor_name=N
warnings = []

if external_id is not None and external_name is not None:
parent_project_data = dict(
**(dict(principal_investigator=principal_investigator) if principal_investigator is not None else dict()),
**(dict(requestor_name=requestor_name) if requestor_name is not None else dict()),
**(dict(requestor_email=requestor_email) if requestor_email is not None else dict()),
)
try:
parent_project = ParentProject.objects.create(external_id=external_id, name=external_name)
parent_project = ParentProject.objects.create(external_id=external_id, name=external_name, **parent_project_data)
except ValidationError as e:
errors.append(str(e))
Comment on lines +52 to 60

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can reuse create_parent_project for this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate how diff colouring gets obscured if you write a comment in lines...


project_data = dict(
name=name,
# Optional attributes
**(dict(principal_investigator=principal_investigator) if principal_investigator is not None else dict()),
**(dict(requestor_name=requestor_name) if requestor_name is not None else dict()),
**(dict(requestor_email=requestor_email) if requestor_email is not None else dict()),
**(dict(parent_project=parent_project) if parent_project is not None else dict()),
**(dict(status=status) if status is not None else dict()),
**(dict(targeted_end_date=targeted_end_date) if targeted_end_date is not None else dict()),
Expand All @@ -73,18 +75,14 @@ def create_full_project(name=None, principal_investigator=None, requestor_name=N

return (project, errors, warnings)

def create_project(name=None, principal_investigator=None, requestor_name=None,
requestor_email=None, parent_project=None, status=None, targeted_end_date=None, comment=None):
def create_project(name=None, parent_project=None, status=None, targeted_end_date=None, comment=None):
project = None
errors = []
warnings = []

project_data = dict(
name=name,
# Optional attributes
**(dict(principal_investigator=principal_investigator) if principal_investigator is not None else dict()),
**(dict(requestor_name=requestor_name) if requestor_name is not None else dict()),
**(dict(requestor_email=requestor_email) if requestor_email is not None else dict()),
**(dict(parent_project=parent_project) if parent_project is not None else dict()),
**(dict(status=status) if status is not None else dict()),
**(dict(targeted_end_date=targeted_end_date) if targeted_end_date is not None else dict()),
Expand All @@ -98,13 +96,18 @@ def create_project(name=None, principal_investigator=None, requestor_name=None,

return (project, errors, warnings)

def create_parent_project(external_id, name):
def create_parent_project(external_id, name, principal_investigator=None, requestor_name=None, requestor_email=None):
parent_project = None
errors = []
warnings = []

parent_project_data = dict(
**(dict(principal_investigator=principal_investigator) if principal_investigator is not None else dict()),
**(dict(requestor_name=requestor_name) if requestor_name is not None else dict()),
**(dict(requestor_email=requestor_email) if requestor_email is not None else dict()),
)
try:
parent_project = ParentProject.objects.create(external_id=external_id, name=name)
parent_project = ParentProject.objects.create(external_id=external_id, name=name, **parent_project_data)
except ValidationError as e:
errors.append(str(e))

Expand Down
36 changes: 10 additions & 26 deletions backend/fms_core/tests/test_models/project_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,17 @@ def setUp(self):

def test_project(self):
my_project = Project.objects.create(name=self.name,
principal_investigator=self.principal_investigator,
requestor_name=self.requestor_name,
requestor_email=self.requestor_email,
targeted_end_date=self.targeted_end_date,
comment=self.comment)
self.assertEqual(my_project.name, self.name)
self.assertEqual(my_project.principal_investigator, self.principal_investigator)
self.assertEqual(my_project.requestor_name, self.requestor_name)
self.assertEqual(my_project.requestor_email, self.requestor_email)
self.assertEqual(my_project.status, self.status)
self.assertEqual(my_project.comment, self.comment)
self.assertEqual(my_project.targeted_end_date, datetime.strptime(self.targeted_end_date, "%Y-%m-%d").date())

def test_missing_name(self):
with self.assertRaises(ValidationError):
try:
er_without_et = Project.objects.create(principal_investigator=self.principal_investigator,
requestor_name=self.requestor_name,
requestor_email=self.requestor_email,
status=self.status)
er_without_et = Project.objects.create(status=self.status)
except ValidationError as e:
self.assertTrue("name" in e.message_dict)
raise e
Expand All @@ -56,16 +47,12 @@ def test_duplicate_project_with_name(self):
with self.assertRaises(ValidationError):
# First Project is valid
Project.objects.create(name=self.duplicate_name,
principal_investigator=self.principal_investigator,
status=self.status,
targeted_end_date=self.targeted_end_date)

try:
# Second Project has the same name, should be invalid
Project.objects.create(name=self.duplicate_name,
principal_investigator=self.principal_investigator,
requestor_name=self.requestor_name,
requestor_email=self.requestor_email,
status=self.status,
targeted_end_date=self.targeted_end_date)
except ValidationError as e:
Expand All @@ -76,16 +63,12 @@ def test_project_with_similar_name(self):
with self.assertRaises(ValidationError):
# First Project is valid
Project.objects.create(name=self.name,
principal_investigator=self.principal_investigator,
status=self.status,
targeted_end_date=self.targeted_end_date)

try:
# Second Project has a similar name, but different upper/lower cases, should be invalid
Project.objects.create(name=self.similar_name,
principal_investigator=self.principal_investigator,
requestor_name=self.requestor_name,
requestor_email=self.requestor_email,
status=self.status,
targeted_end_date=self.targeted_end_date)
except ValidationError as e:
Expand All @@ -94,21 +77,22 @@ def test_project_with_similar_name(self):

def test_empty_parent_project(self):
my_project = Project.objects.create(name=self.name,
principal_investigator=self.principal_investigator,
requestor_name=self.requestor_name,
requestor_email=self.requestor_email,
targeted_end_date=self.targeted_end_date,
comment=self.comment)
self.assertEqual(my_project.parent_project, None)

def test_not_empty_parent_project(self):
parent_project_obj = ParentProject.objects.create(external_id="P000010", name="TESTS")
parent_project_obj = ParentProject.objects.create(external_id="P000010",
name="TESTS",
principal_investigator=self.principal_investigator,
requestor_name=self.requestor_name,
requestor_email=self.requestor_email)
my_project = Project.objects.create(name=self.name,
principal_investigator=self.principal_investigator,
requestor_name=self.requestor_name,
requestor_email=self.requestor_email,
targeted_end_date=self.targeted_end_date,
parent_project=parent_project_obj,
comment=self.comment)
self.assertEqual(my_project.parent_project.external_id, "P000010")
self.assertEqual(my_project.parent_project.name, "TESTS")
self.assertEqual(my_project.parent_project.name, "TESTS")
self.assertEqual(my_project.parent_project.principal_investigator, self.principal_investigator)
self.assertEqual(my_project.parent_project.requestor_name, self.requestor_name)
self.assertEqual(my_project.parent_project.requestor_email, self.requestor_email)
Loading
Loading