Skip to content
Merged
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
3 changes: 2 additions & 1 deletion src/datasets_synchronization/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

class CGDSStudyAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'version', 'date_last_synchronization', 'state')
list_filter = ('state',)
list_filter = ('state', 'tissues')
search_fields = ('name', 'description')
filter_horizontal = ('tissues',)

def delete_queryset(self, request, queryset):
"""
Expand Down
28 changes: 28 additions & 0 deletions src/datasets_synchronization/migrations/0037_cgdsstudy_tissue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.19 on 2026-02-23 00:20

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("tissues", "0001_initial"),
(
"datasets_synchronization",
"0036_alter_cgdsstudy_clinical_patient_dataset_and_more",
),
]

operations = [
migrations.AddField(
model_name="cgdsstudy",
name="tissue",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="tissues.tissue",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.19 on 2026-03-05 22:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("tissues", "0002_tissue_code_and_initial_data"),
("datasets_synchronization", "0037_cgdsstudy_tissue"),
]

operations = [
migrations.RemoveField(
model_name="cgdsstudy",
name="tissue",
),
migrations.AddField(
model_name="cgdsstudy",
name="tissues",
field=models.ManyToManyField(blank=True, to="tissues.tissue"),
),
]
4 changes: 3 additions & 1 deletion src/datasets_synchronization/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from common.methylation import MethylationPlatform
from feature_selection.models import TrainedModel
from statistical_properties.models import StatisticalValidation
from tissues.models import Tissue
from user_files.models import UserFile
from user_files.models_choices import FileType
from pandas import DataFrame
Expand Down Expand Up @@ -288,6 +289,7 @@ class CGDSStudy(models.Model):
null=True,
related_name='cgds_studies_as_clinical_sample_dataset'
)
tissues = models.ManyToManyField(Tissue, blank=True)
task_id: Optional[str] = models.CharField(max_length=100, blank=True, null=True) # Celery Task ID

def __str__(self) -> str:
Expand Down Expand Up @@ -327,4 +329,4 @@ def delete(self, *args, **kwargs) -> None:
dataset.delete()

# Sends a websocket message to update the state in the frontend
send_update_cgds_studies_command()
send_update_cgds_studies_command()
9 changes: 8 additions & 1 deletion src/datasets_synchronization/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,10 @@ def create(self, validated_data) -> Optional[CGDSStudy]:
methylation_dataset = self.__create_cgds_dataset(validated_data.pop('methylation_dataset'))
clinical_patient_dataset = self.__create_cgds_dataset(validated_data.pop('clinical_patient_dataset'))
clinical_sample_dataset = self.__create_cgds_dataset(validated_data.pop('clinical_sample_dataset'))
tissues = validated_data.pop('tissues', [])

# Creates the CGDSStudy
return CGDSStudy.objects.create(
cgds_study = CGDSStudy.objects.create(
mrna_dataset=mrna_dataset,
mirna_dataset=mirna_dataset,
cna_dataset=cna_dataset,
Expand All @@ -235,6 +236,8 @@ def create(self, validated_data) -> Optional[CGDSStudy]:
clinical_sample_dataset=clinical_sample_dataset,
**validated_data
)
cgds_study.tissues.set(tissues)
return cgds_study

@staticmethod
def __set_clinical_datasets_to_existing_experiments(cgds_study: CGDSStudy):
Expand Down Expand Up @@ -302,6 +305,10 @@ def update(self, instance: CGDSStudy, validated_data):
instance.clinical_patient_dataset = clinical_patient_dataset
instance.clinical_sample_dataset = clinical_sample_dataset

# Updates M2M tissues if provided
if 'tissues' in validated_data:
instance.tissues.set(validated_data['tissues'])

# Saves new changes and returns instance
instance.save()
return instance
Expand Down
4 changes: 3 additions & 1 deletion src/datasets_synchronization/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .enums import SyncCGDSStudyResponseCode, SyncStrategy
from .models import CGDSStudy, CGDSDatasetSynchronizationState, CGDSStudySynchronizationState, CGDSDataset
from rest_framework import generics, permissions, filters
from django_filters.rest_framework import DjangoFilterBackend
from user_files.models_choices import FileType
from .serializers import CGDSStudySerializer
from django.shortcuts import render, get_object_or_404
Expand Down Expand Up @@ -82,7 +83,8 @@ def get_queryset(self):
serializer_class = CGDSStudySerializer
permission_classes = [permissions.IsAuthenticated]
pagination_class = StandardResultsSetPagination
filter_backends = [filters.OrderingFilter, filters.SearchFilter]
filter_backends = [filters.OrderingFilter, filters.SearchFilter, DjangoFilterBackend]
filterset_fields = ['tissues']
search_fields = ['name', 'description']
ordering_fields = '__all__'

Expand Down
1 change: 1 addition & 0 deletions src/frontend/templates/frontend/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
const urlTagsCRUD = "{% url 'tags' %}" {# REST CRUD URL User Tags #}
const urlUserFilesCRUD = "{% url 'user_files' %}" {# REST CRUD URL User Files #}
const urlCGDSStudiesCRUD = "{% url 'cgds_studies' %}" {# URL CGDS Studies #}
const urlTissuesCRUD = "{% url 'tissues' %}" {# REST URL for Tissues list #}
const urlCurrentUser = "{% url 'current_user' %}" {# URL to get the current logged user #}
const urlSitePolicy = "{% url 'site_policy' %}" {# URL to Site policy page #}
const urlCGDSPanel = "{% url 'cgds_panel' %}" {# URL to the CGDS Panel #}
Expand Down
1 change: 1 addition & 0 deletions src/multiomics_intermediate/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
'molecules_details',
'chunked_upload',
'users',
'tissues',
]

MIDDLEWARE = [
Expand Down
1 change: 1 addition & 0 deletions src/multiomics_intermediate/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@
path('admin/', admin.site.urls),
path('email/', include(mail_urls)),
path('users/', include('users.urls')),
path('tissues/', include('tissues.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Empty file added src/tissues/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions src/tissues/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.contrib import admin
from .models import Tissue


@admin.register(Tissue)
class TissueAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
search_fields = ('name',)

def has_add_permission(self, request):
return False

def has_change_permission(self, request, obj=None):
return False

def has_delete_permission(self, request, obj=None):
return False
6 changes: 6 additions & 0 deletions src/tissues/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class TissuesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tissues"
31 changes: 31 additions & 0 deletions src/tissues/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.2.19 on 2026-02-23 00:19

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="Tissue",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, unique=True)),
],
options={
"ordering": ["name"],
},
),
]
57 changes: 57 additions & 0 deletions src/tissues/migrations/0002_tissue_code_and_initial_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from django.db import migrations, models

TISSUES = [
('Adrenal Gland', 'ADRENAL_GLAND'),
('Bladder', 'BLADDER'),
('Blood', 'BLOOD'),
('Brain', 'BRAIN'),
('Breast', 'BREAST'),
('Cervix Uteri', 'CERVIX_UTERI'),
('Colon', 'COLON'),
('Esophagus', 'ESOPHAGUS'),
('Kidney', 'KIDNEY'),
('Liver', 'LIVER'),
('Lung', 'LUNG'),
('Ovary', 'OVARY'),
('Pancreas', 'PANCREAS'),
('Prostate', 'PROSTATE'),
('Skin', 'SKIN'),
('Stomach', 'STOMACH'),
('Testis', 'TESTIS'),
('Thyroid', 'THYROID'),
('Uterus', 'UTERUS'),
]


def load_tissues(apps, schema_editor):
Tissue = apps.get_model('tissues', 'Tissue')
Tissue.objects.bulk_create([Tissue(name=name, code=code) for name, code in TISSUES])


def unload_tissues(apps, schema_editor):
Tissue = apps.get_model('tissues', 'Tissue')
Tissue.objects.filter(name__in=[name for name, _ in TISSUES]).delete()


class Migration(migrations.Migration):

dependencies = [
('tissues', '0001_initial'),
]

operations = [
# Add code field as nullable first (so existing rows don't violate constraints)
migrations.AddField(
model_name='tissue',
name='code',
field=models.CharField(max_length=100, null=True, blank=True),
),
# Insert the 19 English tissues (table is empty at this point in migration history)
migrations.RunPython(load_tissues, reverse_code=unload_tissues),
# Make code non-nullable and unique
migrations.AlterField(
model_name='tissue',
name='code',
field=models.CharField(max_length=100, unique=True),
),
]
Empty file.
17 changes: 17 additions & 0 deletions src/tissues/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.core.exceptions import PermissionDenied
from django.db import models


class Tissue(models.Model):
"""Reference model for tissue types. Tissues are read-only and cannot be deleted."""
name = models.CharField(max_length=100, unique=True)
code = models.CharField(max_length=100, unique=True) # noqa: populated by migration 0003

class Meta:
ordering = ['name']

def __str__(self) -> str:
return self.name

def delete(self, *args, **kwargs):
raise PermissionDenied("Tissues cannot be deleted")
8 changes: 8 additions & 0 deletions src/tissues/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from rest_framework import serializers
from .models import Tissue


class TissueSerializer(serializers.ModelSerializer):
class Meta:
model = Tissue
fields = ['id', 'name', 'code']
6 changes: 6 additions & 0 deletions src/tissues/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.urls import path
from . import views

urlpatterns = [
path('', views.TissueList.as_view(), name='tissues'),
]
12 changes: 12 additions & 0 deletions src/tissues/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from rest_framework import generics, permissions, filters
from .models import Tissue
from .serializers import TissueSerializer


class TissueList(generics.ListAPIView):
"""REST endpoint: read-only list for Tissue model."""
queryset = Tissue.objects.all()
serializer_class = TissueSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.SearchFilter]
search_fields = ['name']
10 changes: 8 additions & 2 deletions src/user_files/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@

class UserFileAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'file_type', 'upload_date', 'contains_nan_values', 'number_of_rows',
'number_of_samples', 'decimal_separator', 'is_public', 'is_cpg_site_id', 'platform')
list_filter = ('file_type', 'upload_date', 'is_public', 'is_cpg_site_id')
'number_of_samples', 'decimal_separator', 'is_public', 'is_cpg_site_id', 'platform',
'tissue_list')
list_filter = ('file_type', 'upload_date', 'is_public', 'is_cpg_site_id', 'tissues')
search_fields = ('name', 'description', 'user__username')
filter_horizontal = ('tissues',)

def tissue_list(self, obj):
return ', '.join(obj.tissues.values_list('name', flat=True))
tissue_list.short_description = 'Tissues'


admin.site.register(UserFile, UserFileAdmin)
25 changes: 25 additions & 0 deletions src/user_files/migrations/0016_userfile_tissue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.19 on 2026-02-23 00:20

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("tissues", "0001_initial"),
("user_files", "0015_alter_userfile_options"),
]

operations = [
migrations.AddField(
model_name="userfile",
name="tissue",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="tissues.tissue",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.19 on 2026-03-05 22:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("tissues", "0002_tissue_code_and_initial_data"),
("user_files", "0016_userfile_tissue"),
]

operations = [
migrations.RemoveField(
model_name="userfile",
name="tissue",
),
migrations.AddField(
model_name="userfile",
name="tissues",
field=models.ManyToManyField(blank=True, to="tissues.tissue"),
),
]
Loading
Loading