Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feature/CSCKAN-276
Browse files Browse the repository at this point in the history
  • Loading branch information
D-GopalKrishna committed Apr 10, 2024
2 parents 184e4f2 + f2b276a commit 611f415
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 81 deletions.
46 changes: 32 additions & 14 deletions backend/composer/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import Any
from django.db.models.query import QuerySet
from django.http import HttpRequest
import nested_admin
from adminsortable2.admin import SortableAdminBase, SortableStackedInline
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from fsm_admin.mixins import FSMTransitionMixin
from django import forms

from composer.models import (
Phenotype,
Expand Down Expand Up @@ -38,6 +42,10 @@ class ProvenanceInline(admin.StackedInline):
model = Provenance
extra = 1

class SynonymInline(admin.StackedInline):
model = Synonym
extra = 1


class ProvenanceNestedInline(nested_admin.NestedStackedInline):
model = Provenance
Expand Down Expand Up @@ -89,26 +97,32 @@ class SentenceAdmin(
NoteSentenceInline,
)


class SynonymInline(admin.TabularInline):
model = Synonym
extra = 1


class AnatomicalEntityAdmin(admin.ModelAdmin):
search_fields = ('simple_entity__name', 'region_layer__layer__name', 'region_layer__region__name')
autocomplete_fields = ('simple_entity', 'region_layer')
list_display = ('simple_entity', 'region_layer', "synonyms")
list_display_links = ('simple_entity', 'region_layer')
inlines = (SynonymInline,)

def get_model_perms(self, request):
"""
Return empty dict to hide the model from admin index.
"""
return {}
# we need to make efficient queries to the database to get the list of anatomical entities
def get_queryset(self, request: HttpRequest) -> QuerySet[Any]:
return super().get_queryset(request) \
.select_related('simple_entity', 'region_layer__layer', 'region_layer__region') \
.prefetch_related('synonyms')

@admin.display(description="Synonyms")
def synonyms(self, obj):
synonyms = obj.synonyms.all()
return ', '.join([synonym.name for synonym in synonyms])


class AnatomicalEntityMetaAdmin(admin.ModelAdmin):
list_display = ("name", "ontology_uri")
list_display_links = ("name", "ontology_uri")
search_fields = ("name",)
search_fields = ("name", "ontology_uri")

def get_model_perms(self, request):
return {}


class LayerAdmin(admin.ModelAdmin):
Expand All @@ -122,11 +136,15 @@ class RegionAdmin(admin.ModelAdmin):
filter_horizontal = ('layers',)


class AnatomicalEntityIntersectionAdmin(admin.ModelAdmin):
list_display = ('layer', 'region',)
class AnatomicalEntityIntersectionAdmin(nested_admin.NestedModelAdmin, admin.ModelAdmin):
list_display = ('layer', 'region')
search_fields = ("region__name", "layer__name")
list_filter = ('layer', 'region',)
raw_id_fields = ('layer', 'region',)

def get_model_perms(self, request):
return {}


class ViaInline(SortableStackedInline):
model = Via
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.1.4 on 2024-04-03 13:57

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


class Migration(migrations.Migration):

dependencies = [
("composer", "0050_alter_anatomicalentityintersection_options_and_more"),
]

operations = [
migrations.AlterField(
model_name="anatomicalentity",
name="region_layer",
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="composer.anatomicalentityintersection",
),
),
]
24 changes: 16 additions & 8 deletions backend/composer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,28 +255,35 @@ class Meta:
verbose_name_plural = "Region/Layer Combinations"

def __str__(self):
return f"{self.region.name} - {self.layer.name}"
return f'{self.region.name} - {self.layer.name}'


class AnatomicalEntity(models.Model):
simple_entity = models.OneToOneField(AnatomicalEntityMeta, on_delete=models.CASCADE, null=True, blank=True)
region_layer = models.ForeignKey(AnatomicalEntityIntersection, on_delete=models.CASCADE, null=True, blank=True)
region_layer = models.OneToOneField(AnatomicalEntityIntersection, on_delete=models.CASCADE, null=True, blank=True)

@property
def name(self):
return self.simple_entity.name if self.simple_entity \
else f'{self.region_layer.region.name},{self.region_layer.layer.name}'
if self.simple_entity:
return self.simple_entity.name
elif self.region_layer:
return f'{self.region_layer.region.name},{self.region_layer.layer.name}'
return 'Unknown Anatomical Entity'

@property
def ontology_uri(self):
return self.simple_entity.ontology_uri if self.simple_entity \
else f'{self.region_layer.region.ontology_uri},{self.region_layer.layer.ontology_uri}'

if self.simple_entity:
return self.simple_entity.ontology_uri
elif self.region_layer:
return f'{self.region_layer.region.ontology_uri},{self.region_layer.layer.ontology_uri}'
return 'Unknown URI'

def __str__(self):
return self.name

class Meta:
verbose_name = "Anatomical Entity"
verbose_name_plural = "Anatomical Entities"
constraints = [
CheckConstraint(
check=(
Expand Down Expand Up @@ -519,7 +526,8 @@ def __str__(self):
source=[
CSState.DRAFT,
CSState.IN_PROGRESS,
CSState.INVALID
CSState.INVALID,
CSState.EXPORTED,
],
target=CSState.COMPOSE_NOW,
permission=ConnectivityStatementStateService.has_permission_to_transition_to_compose_now,
Expand Down
16 changes: 7 additions & 9 deletions backend/composer/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from django_fsm.signals import post_transition

from .enums import CSState, NoteType
from .models import ConnectivityStatement, ExportBatch, Note, Sentence, AnatomicalEntity, AnatomicalEntityIntersection, \
AnatomicalEntityMeta
from .models import ConnectivityStatement, ExportBatch, Note, Sentence, Synonym, \
AnatomicalEntity, Layer, Region
from .services.export_services import compute_metrics, ConnectivityStatementStateService


Expand Down Expand Up @@ -48,14 +48,12 @@ def post_transition_cs(sender, instance, name, source, target, **kwargs):
# add important tag to CS when transition to COMPOSE_NOW from NPO Approved or Exported
instance = ConnectivityStatementStateService.add_important_tag(instance)


@receiver(post_save, sender=AnatomicalEntityIntersection)
def create_region_layer_anatomical_entity(sender, instance=None, created=False, **kwargs):
@receiver(post_save, sender=Layer)
def create_layer_anatomical_entity(sender, instance=None, created=False, **kwargs):
if created and instance:
AnatomicalEntity.objects.create(region_layer=instance)

AnatomicalEntity.objects.create(simple_entity=instance)

@receiver(post_save, sender=AnatomicalEntityMeta)
def create_simple_anatomical_entity(sender, instance=None, created=False, **kwargs):
@receiver(post_save, sender=Region)
def create_region_anatomical_entity(sender, instance=None, created=False, **kwargs):
if created and instance:
AnatomicalEntity.objects.create(simple_entity=instance)
8 changes: 4 additions & 4 deletions frontend/src/apiclient/backend/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2216,7 +2216,7 @@ export const ComposerApiAxiosParamCreator = function (configuration?: Configurat
},
/**
* AnatomicalEntity
* @param {number} id A unique integer value identifying this anatomical entity.
* @param {number} id A unique integer value identifying this Anatomical Entity.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
Expand Down Expand Up @@ -4730,7 +4730,7 @@ export const ComposerApiFp = function(configuration?: Configuration) {
},
/**
* AnatomicalEntity
* @param {number} id A unique integer value identifying this anatomical entity.
* @param {number} id A unique integer value identifying this Anatomical Entity.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
Expand Down Expand Up @@ -5333,7 +5333,7 @@ export const ComposerApiFactory = function (configuration?: Configuration, baseP
},
/**
* AnatomicalEntity
* @param {number} id A unique integer value identifying this anatomical entity.
* @param {number} id A unique integer value identifying this Anatomical Entity.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
Expand Down Expand Up @@ -5885,7 +5885,7 @@ export class ComposerApi extends BaseAPI {

/**
* AnatomicalEntity
* @param {number} id A unique integer value identifying this anatomical entity.
* @param {number} id A unique integer value identifying this Anatomical Entity.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ComposerApi
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/components/Forms/StatementForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const StatementForm = (props: any) => {
await api.composerViaDestroy(element.children.props.formData.id);
refreshStatement();
}}
onElementAdd={async (element: any) => {
onElementAdd={async () => {
await api.composerViaCreate({
id: -1,
order: statement?.vias?.length,
Expand Down Expand Up @@ -354,7 +354,7 @@ const StatementForm = (props: any) => {
},
refreshStatement: () => refreshStatement(),
errors: "",
mapValueToOption: (anatomicalEntities: any[], formId: any) => {
mapValueToOption: (anatomicalEntities: any[]) => {
const entities: Option[] = [];
const selected = findMatchingEntities(
statement,
Expand Down Expand Up @@ -386,7 +386,7 @@ const StatementForm = (props: any) => {
);
refreshStatement();
}}
onElementAdd={async (element: any) => {
onElementAdd={async () => {
await api.composerDestinationCreate({
id: -1,
connectivity_statement: statement.id,
Expand Down Expand Up @@ -536,7 +536,7 @@ const StatementForm = (props: any) => {
},
refreshStatement: () => refreshStatement(),
errors: "",
mapValueToOption: (anatomicalEntities: any[], formId: any) => {
mapValueToOption: (anatomicalEntities: any[]) => {
const entities: Option[] = [];
const selected = findMatchingEntities(
statement,
Expand Down Expand Up @@ -573,6 +573,7 @@ const StatementForm = (props: any) => {
copiedUISchema.forward_connection = {
"ui:widget": CustomEntitiesDropdown,
"ui:options": {
chipsNumber: 10,
isDisabled,
placeholder: "Forward connection(s)",
searchPlaceholder: "Search for Connectivity Statements",
Expand Down
69 changes: 37 additions & 32 deletions frontend/src/components/Widgets/CustomChipBoxComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {Box, Chip, Tooltip} from "@mui/material";
import {Option} from "../../types";
import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined";
import React from "react";

const CustomChipBoxComponent = ({
selectedOptions,
CustomInputChip,
Expand All @@ -21,41 +20,47 @@ const CustomChipBoxComponent = ({
width: 'fit-content',
maxWidth: 'fit-content',
}

const renderChipOrCustomInput = (item: Option) => (
<Tooltip
title={item?.label}
placement="top"
arrow
key={item.id}
>
{CustomInputChip ? (
<CustomInputChip sx={styles.chip} entity={item} />
) : (
<Chip
sx={{
...styles.chip,
...extraChipStyle
}}
variant={"outlined"}
onClick={(e) => {
e.stopPropagation();
}}
deleteIcon={<ClearOutlinedIcon />}
onDelete={!isDisabled ? (e) => {
e.stopPropagation();
handleChipRemove(item);
} : undefined}
label={item?.label}
/>
)}
</Tooltip>
);


return (
<Box gap={1} display="flex" flexWrap="wrap" alignItems="center">
{selectedOptions?.length ? (
{!isDisabled && selectedOptions?.length ? (
selectedOptions
.slice(0, chipsNumber)
.map((item: Option, index: number) => (
<Tooltip
title={item?.label}
placement="top"
arrow
key={item.id}
>
{CustomInputChip ? (
<CustomInputChip sx={styles.chip} entity={item} />
) : (
<Chip
sx={{
...styles.chip,
...extraChipStyle
}}
variant={"outlined"}
onClick={(e) => {
e.stopPropagation();
}}
deleteIcon={<ClearOutlinedIcon />}
onDelete={!isDisabled ? (e) => {
e.stopPropagation();
handleChipRemove(item);
} : undefined}
label={item?.label}
/>
)}
</Tooltip>
))
) : null}
.map((item: Option) => renderChipOrCustomInput(item))
) : (
selectedOptions?.map((item: Option) => renderChipOrCustomInput(item))
)}
{!isDisabled && selectedOptions.length > chipsNumber && (
<span style={{ marginRight: ".5rem" }}>
+{selectedOptions.length - chipsNumber}
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/components/Widgets/CustomEntitiesDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ const styles = {
height: "1.5rem",
borderRadius: "0.375rem",
fontSize: "0.875rem",
maxWidth: "8rem",
fontWeight: 500,

"&.MuiChip-filled": {
Expand Down Expand Up @@ -489,7 +488,6 @@ export default function CustomEntitiesDropdown({
gap={1}
sx={{
borderBottom: `0.0625rem solid ${popperBorderColor}`,
height: autocompleteOptions.length > 0 ? "2.75rem" : "auto",
padding:
autocompleteOptions.length > 0 ? "0 0.875rem" : "0.875rem",
}}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/helpers/anatomicalEntityHelper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {AnatomicalEntity} from "../apiclient/backend";
import { AnatomicalEntity } from "../apiclient/backend";
import { OptionDetail } from "../types";

export const DROPDOWN_MAPPER_ONTOLOGY_URL = "Ontology URI";
Expand Down
Loading

0 comments on commit 611f415

Please sign in to comment.