Skip to content

Commit 9d7ee56

Browse files
authored
configuration setup (#119)
* configuration setup * add configuration options, update temporal sequence displaying
1 parent 34b4a95 commit 9d7ee56

File tree

17 files changed

+487
-177
lines changed

17 files changed

+487
-177
lines changed

bats_ai/api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from oauth2_provider.models import AccessToken
55

66
from bats_ai.core.views import (
7+
ConfigurationRouter,
78
GRTSCellsRouter,
89
GuanoMetadataRouter,
910
RecordingAnnotationRouter,
@@ -35,3 +36,4 @@ def global_auth(request):
3536
api.add_router('/grts/', GRTSCellsRouter)
3637
api.add_router('/guano/', GuanoMetadataRouter)
3738
api.add_router('/recording-annotation/', RecordingAnnotationRouter)
39+
api.add_router('/configuration/', ConfigurationRouter)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 4.1.13 on 2025-01-03 15:14
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
('core', '0012_recordingannotation_additional_data'),
9+
]
10+
11+
operations = [
12+
migrations.CreateModel(
13+
name='Configuration',
14+
fields=[
15+
(
16+
'id',
17+
models.BigAutoField(
18+
auto_created=True, primary_key=True, serialize=False, verbose_name='ID'
19+
),
20+
),
21+
('display_pulse_annotations', models.BooleanField(default=True)),
22+
('display_sequence_annotations', models.BooleanField(default=True)),
23+
],
24+
),
25+
]

bats_ai/core/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .annotations import Annotations
22
from .compressed_spectrogram import CompressedSpectrogram
3+
from .configuration import Configuration
34
from .grts_cells import GRTSCells
45
from .image import Image
56
from .recording import Recording, colormap
@@ -21,4 +22,5 @@
2122
'colormap',
2223
'CompressedSpectrogram',
2324
'RecordingAnnotation',
25+
'Configuration',
2426
]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from django.db import models
2+
from django.db.models.signals import post_migrate
3+
from django.dispatch import receiver
4+
5+
6+
# Define the Configuration model
7+
class Configuration(models.Model):
8+
display_pulse_annotations = models.BooleanField(default=True)
9+
display_sequence_annotations = models.BooleanField(default=True)
10+
11+
def save(self, *args, **kwargs):
12+
# Ensure only one instance of Configuration exists
13+
if not Configuration.objects.exists() and not self.pk:
14+
super().save(*args, **kwargs)
15+
elif self.pk:
16+
super().save(*args, **kwargs)
17+
else:
18+
raise ValueError('Only one instance of Configuration is allowed.')
19+
20+
21+
# Automatically create a Configuration instance after migrations
22+
@receiver(post_migrate)
23+
def create_default_configuration(sender, **kwargs):
24+
if not Configuration.objects.exists():
25+
Configuration.objects.create()

bats_ai/core/views/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .annotations import router as AnnotationRouter
2+
from .configuration import router as ConfigurationRouter
23
from .grts_cells import router as GRTSCellsRouter
34
from .guanometadata import router as GuanoMetadataRouter
45
from .recording import router as RecordingRouter
@@ -14,4 +15,5 @@
1415
'GRTSCellsRouter',
1516
'GuanoMetadataRouter',
1617
'RecordingAnnotationRouter',
18+
'ConfigurationRouter',
1719
]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import logging
2+
3+
from django.http import JsonResponse
4+
from ninja import Schema
5+
from ninja.pagination import RouterPaginated
6+
7+
from bats_ai.core.models import Configuration
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
router = RouterPaginated()
13+
14+
15+
# Define schema for the Configuration data
16+
class ConfigurationSchema(Schema):
17+
display_pulse_annotations: bool
18+
display_sequence_annotations: bool
19+
is_admin: bool | None = None
20+
21+
22+
# Endpoint to retrieve the configuration status
23+
@router.get('/', response=ConfigurationSchema)
24+
def get_configuration(request):
25+
config = Configuration.objects.first()
26+
if not config:
27+
return JsonResponse({'error': 'No configuration found'}, status=404)
28+
return ConfigurationSchema(
29+
display_pulse_annotations=config.display_pulse_annotations,
30+
display_sequence_annotations=config.display_sequence_annotations,
31+
is_admin=request.user.is_authenticated and request.user.is_superuser,
32+
)
33+
34+
35+
# Endpoint to update the configuration (admin only)
36+
@router.patch('/')
37+
def update_configuration(request, payload: ConfigurationSchema):
38+
if not request.user.is_authenticated or not request.user.is_superuser:
39+
return JsonResponse({'error': 'Permission denied'}, status=403)
40+
config = Configuration.objects.first()
41+
if not config:
42+
return JsonResponse({'error': 'No configuration found'}, status=404)
43+
for attr, value in payload.dict().items():
44+
setattr(config, attr, value)
45+
config.save()
46+
return ConfigurationSchema.from_orm(config)
47+
48+
49+
@router.get('/is_admin/')
50+
def check_is_admin(request):
51+
if request.user.is_authenticated:
52+
return {'is_admin': request.user.is_superuser}
53+
return {'is_admin': False}

bats_ai/core/views/temporal_annotations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class TemporalAnnotationSchema(Schema):
1212
id: int
1313
start_time: int
1414
end_time: int
15-
type: str
15+
type: str | None
1616
comments: str
1717
species: list[SpeciesSchema] | None
1818
owner_email: str = None

client/src/App.vue

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,22 @@ export default defineComponent({
1212
const oauthClient = inject<OAuthClient>("oauthClient");
1313
const router = useRouter();
1414
const route = useRoute();
15-
const { nextShared, sharedList, sideTab, } = useState();
15+
const { nextShared, sharedList, sideTab, loadConfiguration, configuration } = useState();
1616
const getShared = async () => {
1717
sharedList.value = (await getRecordings(true)).data;
1818
};
19-
if (sharedList.value.length === 0) {
20-
getShared();
21-
}
2219
if (oauthClient === undefined) {
2320
throw new Error('Must provide "oauthClient" into component.');
2421
}
2522
2623
const loginText = ref("Login");
27-
const checkLogin = () => {
24+
const checkLogin = async () => {
2825
if (oauthClient.isLoggedIn) {
2926
loginText.value = "Logout";
27+
loadConfiguration();
28+
if (sharedList.value.length === 0) {
29+
getShared();
30+
}
3031
} else {
3132
loginText.value = "Login";
3233
}
@@ -57,7 +58,18 @@ export default defineComponent({
5758
}
5859
});
5960
60-
return { oauthClient, containsSpectro, loginText, logInOrOut, activeTab, nextShared, sideTab };
61+
const isAdmin = computed(() => configuration.value.is_admin);
62+
63+
return {
64+
oauthClient,
65+
containsSpectro,
66+
loginText,
67+
logInOrOut,
68+
activeTab,
69+
nextShared,
70+
sideTab,
71+
isAdmin,
72+
};
6173
},
6274
});
6375
</script>
@@ -88,6 +100,13 @@ export default defineComponent({
88100
>
89101
Spectrogram
90102
</v-tab>
103+
<v-tab
104+
v-show="isAdmin"
105+
to="/admin"
106+
value="admin"
107+
>
108+
Admin
109+
</v-tab>
91110
</v-tabs>
92111
<v-spacer />
93112
<v-tooltip

client/src/api/api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,21 @@ async function getCellfromLocation(latitude: number, longitude: number) {
357357
return axiosInstance.get<CellIDReponse>(`/grts/grid_cell_id`, {params: {latitude, longitude}});
358358
}
359359

360+
export interface ConfigurationSettings {
361+
display_pulse_annotations: boolean;
362+
display_sequence_annotations: boolean;
363+
is_admin: boolean;
364+
}
365+
366+
export type Configuration = ConfigurationSettings & { is_admin : boolean };
367+
async function getConfiguration() {
368+
return axiosInstance.get<Configuration>('/configuration/');
369+
}
370+
371+
async function patchConfiguration(config: ConfigurationSettings) {
372+
return axiosInstance.patch('/configuration/', {...config });
373+
}
374+
360375
interface GuanoMetadata {
361376
nabat_grid_cell_grts_id?: string
362377
nabat_latitude?: number
@@ -409,4 +424,6 @@ export {
409424
putFileAnnotation,
410425
patchFileAnnotation,
411426
deleteFileAnnotation,
427+
getConfiguration,
428+
patchConfiguration,
412429
};

client/src/components/AnnotationList.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { defineComponent, PropType } from "vue";
2+
import { computed, defineComponent, PropType } from "vue";
33
import { SpectroInfo } from './geoJS/geoJSUtils';
44
import useState from "../use/useState";
55
import { watch, ref } from "vue";
@@ -32,7 +32,7 @@ export default defineComponent({
3232
},
3333
emits: ['select', 'update:annotation', 'delete:annotation'],
3434
setup() {
35-
const { creationType, annotationState, setAnnotationState, annotations, temporalAnnotations, selectedId, selectedType, setSelectedId, sideTab } = useState();
35+
const { creationType, annotationState, setAnnotationState, annotations, temporalAnnotations, selectedId, selectedType, setSelectedId, sideTab, configuration } = useState();
3636
const tab = ref('recording');
3737
const scrollToId = (id: number) => {
3838
const el = document.getElementById(`annotation-${id}`);
@@ -49,6 +49,8 @@ export default defineComponent({
4949
watch(selectedType, () => {
5050
tab.value = selectedType.value;
5151
});
52+
const pulseEnabled = computed(() => configuration.value.display_pulse_annotations);
53+
const sequenceEnabled = computed(() => configuration.value.display_sequence_annotations);
5254
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5355
const tabSwitch = (event: any) => {
5456
// On tab switches we want to deselect the curret annotation
@@ -73,6 +75,8 @@ export default defineComponent({
7375
tabSwitch,
7476
tab,
7577
sideTab,
78+
pulseEnabled,
79+
sequenceEnabled,
7680
};
7781
},
7882
});
@@ -105,6 +109,7 @@ export default defineComponent({
105109
<span>Recording/File Level Species Annotations</span>
106110
</v-tooltip>
107111
<v-tooltip
112+
v-if="sequenceEnabled"
108113
location="bottom"
109114
open-delay="400"
110115
>
@@ -120,6 +125,7 @@ export default defineComponent({
120125
<span>Sequence Level annotations (Approach/Search/Terminal/Social)</span>
121126
</v-tooltip>
122127
<v-tooltip
128+
v-if="pulseEnabled"
123129
location="bottom"
124130
open-delay="400"
125131
>

0 commit comments

Comments
 (0)