• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cortex-lab / alyx / 13545595881

26 Feb 2025 01:59PM UTC coverage: 84.933% (+0.9%) from 84.082%
13545595881

push

github

k1o0
Resolves issue #917

Weighing form subjects list has dead subjects last
WaterAdministration form lists dead subjects at end
WaterAdministration form and save methods assert that admin date before
death date

8072 of 9504 relevant lines covered (84.93%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.05
alyx/actions/admin.py
1
import base64
1✔
2
import json
1✔
3
import structlog
1✔
4

5
from django import forms
1✔
6
from django.conf import settings
1✔
7
from django.contrib import admin
1✔
8
from django.contrib.auth import get_user_model
1✔
9
from django.db.models import Case, When
1✔
10
from django.urls import reverse
1✔
11
from django.utils.html import format_html
1✔
12
from django_admin_listfilter_dropdown.filters import RelatedDropdownFilter
1✔
13
from django.contrib.admin import TabularInline
1✔
14
from rangefilter.filters import DateRangeFilter
1✔
15

16
from alyx.base import (BaseAdmin, DefaultListFilter, BaseInlineAdmin, get_admin_url)
1✔
17
from .models import (OtherAction, ProcedureType, Session, EphysSession, Surgery, VirusInjection,
1✔
18
                     WaterAdministration, WaterRestriction, Weighing, WaterType,
19
                     Notification, NotificationRule, Cull, CullReason, CullMethod, ImagingSession
20
                     )
21
from data.models import Dataset, FileRecord
1✔
22
from misc.admin import NoteInline
1✔
23
from subjects.models import Subject
1✔
24
from .water_control import WaterControl
1✔
25
from experiments.models import ProbeInsertion, FOV
1✔
26
from jobs.models import Task
1✔
27

28
logger = structlog.get_logger(__name__)
1✔
29

30

31
# Filters
32
# ------------------------------------------------------------------------------------------------
33

34
class ResponsibleUserListFilter(DefaultListFilter):
1✔
35
    title = 'responsible user'
1✔
36
    parameter_name = 'responsible_user'
1✔
37

38
    def lookups(self, request, model_admin):
1✔
39
        return (
1✔
40
            (None, 'Me'),
41
            ('all', 'All'),
42
        )
43

44
    def queryset(self, request, queryset):
1✔
45
        if self.value() is None:
1✔
46
            return queryset.filter(subject__responsible_user=request.user)
1✔
47
        elif self.value == 'all':
×
48
            return queryset.all()
×
49

50

51
class SubjectAliveListFilter(DefaultListFilter):
1✔
52
    title = 'alive'
1✔
53
    parameter_name = 'alive'
1✔
54

55
    def lookups(self, request, model_admin):
1✔
56
        return (
1✔
57
            (None, 'Yes'),
58
            ('n', 'No'),
59
            ('all', 'All'),
60
        )
61

62
    def queryset(self, request, queryset):
1✔
63
        if self.value() is None:
1✔
64
            return queryset.filter(subject__cull__isnull=True)
1✔
65
        if self.value() == 'n':
×
66
            return queryset.exclude(subject__cull__isnull=True)
×
67
        elif self.value == 'all':
×
68
            return queryset.all()
×
69

70

71
class ActiveFilter(DefaultListFilter):
1✔
72
    title = 'active'
1✔
73
    parameter_name = 'active'
1✔
74

75
    def lookups(self, request, model_admin):
1✔
76
        return (
1✔
77
            (None, 'All'),
78
            ('active', 'Active'),
79
        )
80

81
    def queryset(self, request, queryset):
1✔
82
        if self.value() == 'active':
1✔
83
            return queryset.filter(start_time__isnull=False,
×
84
                                   end_time__isnull=True,
85
                                   )
86
        elif self.value is None:
1✔
87
            return queryset.all()
×
88

89

90
class CreatedByListFilter(DefaultListFilter):
1✔
91
    title = 'users'
1✔
92
    parameter_name = 'users'
1✔
93

94
    def lookups(self, request, model_admin):
1✔
95
        return (
×
96
            (None, 'Me'),
97
            ('all', 'All'),
98
        )
99

100
    def queryset(self, request, queryset):
1✔
101
        if self.value() is None:
×
102
            return queryset.filter(users=request.user)
×
103
        elif self.value == 'all':
×
104
            return queryset.all()
×
105

106

107
def _bring_to_front(ids, id):
1✔
108
    if id in ids:
×
109
        ids.remove(id)
×
110
    return [id] + ids
×
111

112

113
# Admin
114
# ------------------------------------------------------------------------------------------------
115
class BaseActionForm(forms.ModelForm):
1✔
116
    def __init__(self, *args, **kwargs):
1✔
117
        super(BaseActionForm, self).__init__(*args, **kwargs)
1✔
118
        if 'users' in self.fields:
1✔
119
            self.fields['users'].queryset = get_user_model().objects.all().order_by('username')
1✔
120
        if 'user' in self.fields:
1✔
121
            self.fields['user'].queryset = get_user_model().objects.all().order_by('username')
1✔
122
        # restricts the subject choices only to managed subjects
123
        if 'subject' in self.fields and not (
1✔
124
                self.current_user.is_stock_manager or self.current_user.is_superuser):
125
            inst = self.instance
1✔
126
            ids = [s.id for s in Subject.objects.filter(responsible_user=self.current_user,
1✔
127
                                                        cull__isnull=True).order_by('nickname')]
128
            if getattr(inst, 'subject', None):
1✔
129
                ids = _bring_to_front(ids, inst.subject.pk)
×
130
            if getattr(self, 'last_subject_id', None):
1✔
131
                ids = _bring_to_front(ids, self.last_subject_id)
×
132
            # These ids first in the list of subjects.
133
            if ids:
1✔
134
                preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(ids)])
1✔
135
                self.fields['subject'].queryset = Subject.objects.filter(
1✔
136
                    pk__in=ids).order_by(preserved, 'nickname')
137
            else:
138
                self.fields['subject'].queryset = Subject.objects.filter(
×
139
                    cull__isnull=True).order_by('nickname')
140

141

142
class BaseActionAdmin(BaseAdmin):
1✔
143
    fields = ['subject', 'start_time', 'end_time', 'users',
1✔
144
              'location', 'lab', 'procedures', 'narrative']
145
    readonly_fields = ['subject_l']
1✔
146

147
    form = BaseActionForm
1✔
148

149
    def subject_l(self, obj):
1✔
150
        url = get_admin_url(obj.subject)
1✔
151
        return format_html('<a href="{url}">{subject}</a>', subject=obj.subject or '-', url=url)
1✔
152
    subject_l.short_description = 'subject'
1✔
153
    subject_l.admin_order_field = 'subject__nickname'
1✔
154

155
    def projects(self, obj):
1✔
156
        return ', '.join(p.name for p in obj.subject.projects.all())
1✔
157

158
    def _get_last_subject(self, request):
1✔
159
        return getattr(request, 'session', {}).get('last_subject_id', None)
1✔
160

161
    def get_form(self, request, obj=None, **kwargs):
1✔
162
        form = super(BaseActionAdmin, self).get_form(request, obj, **kwargs)
1✔
163
        form.current_user = request.user
1✔
164
        form.last_subject_id = self._get_last_subject(request)
1✔
165
        return form
1✔
166

167
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
1✔
168
        # Logged-in user by default.
169
        if db_field.name == 'user':
1✔
170
            kwargs['initial'] = request.user
1✔
171
        if db_field.name == 'subject':
1✔
172
            subject_id = self._get_last_subject(request)
1✔
173
            if subject_id:
1✔
174
                subject = Subject.objects.filter(id=subject_id).first()
1✔
175
                if subject:
1✔
176
                    kwargs['initial'] = subject
1✔
177
        return super(BaseActionAdmin, self).formfield_for_foreignkey(
1✔
178
            db_field, request, **kwargs
179
        )
180

181
    def formfield_for_manytomany(self, db_field, request, **kwargs):
1✔
182
        # Logged-in user by default.
183
        if db_field.name == 'users':
1✔
184
            kwargs['initial'] = [request.user]
1✔
185
        return super(BaseActionAdmin, self).formfield_for_manytomany(
1✔
186
            db_field, request, **kwargs
187
        )
188

189
    def save_model(self, request, obj, form, change):
1✔
190
        subject = getattr(obj, 'subject', None)
×
191
        if subject:
×
192
            getattr(request, 'session', {})['last_subject_id'] = subject.id.hex
×
193
        super(BaseActionAdmin, self).save_model(request, obj, form, change)
×
194

195

196
class OtherActionAdmin(BaseActionAdmin):
1✔
197
    list_display = ['subject_l', 'start_time', 'procedures_l', 'users_l', 'narrative', 'projects']
1✔
198
    list_select_related = ('subject',)
1✔
199
    ordering = ('-start_time', 'subject__nickname')
1✔
200
    search_fields = ['subject__nickname', 'subject__projects__name']
1✔
201
    list_filter = [ResponsibleUserListFilter,
1✔
202
                   ('subject', RelatedDropdownFilter),
203
                   ('users', RelatedDropdownFilter),
204
                   ('start_time', DateRangeFilter),
205
                   ('end_time', DateRangeFilter),
206
                   ]
207

208
    def users_l(self, obj):
1✔
209
        return ', '.join(map(str, obj.users.all()))
×
210

211
    def procedures_l(self, obj):
1✔
212
        return ', '.join(map(str, obj.procedures.all()))
×
213

214
    users_l.short_description = 'users'
1✔
215
    procedures_l.short_description = 'proformed procedures'
1✔
216

217

218
class ProcedureTypeAdmin(BaseActionAdmin):
1✔
219
    fields = ['name', 'description']
1✔
220
    ordering = ['name']
1✔
221

222

223
class WaterAdministrationForm(forms.ModelForm):
1✔
224
    def __init__(self, *args, **kwargs):
1✔
225
        super(WaterAdministrationForm, self).__init__(*args, **kwargs)
1✔
226
        # Show subjects that are on water restriction first.
227
        qs = (WaterRestriction
1✔
228
              .objects
229
              .select_related('subject')
230
              .filter(
231
                  start_time__isnull=False, end_time__isnull=True, subject__death_date__isnull=True
232
              )
233
              .order_by('subject__nickname'))
234
        ids = list(qs.values_list('subject', flat=True))
1✔
235
        if getattr(self, 'last_subject_id', None):
1✔
236
            ids = [self.last_subject_id] + ids
1✔
237
        # These ids first in the list of subjects, if any ids
238
        if not self.fields:
1✔
239
            return
×
240
        if ids:
1✔
241
            preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(ids)])
1✔
242
            subjects = Subject.objects.order_by(preserved, 'nickname')
1✔
243
        else:
244
            subjects = Subject.objects.order_by('nickname')
1✔
245
        # filters the subjects by the current user: responsible users, allowed users
246
        self.fields['subject'].queryset = self.current_user.get_allowed_subjects(subjects)
1✔
247
        self.fields['user'].queryset = get_user_model().objects.all().order_by('username')
1✔
248
        self.fields['water_administered'].widget.attrs.update({'autofocus': 'autofocus'})
1✔
249

250
    def clean(self):
1✔
251
        cleaned_data = super(WaterAdministrationForm, self).clean()
1✔
252
        if cleaned_data['subject']:
1✔
253
            death_date = cleaned_data['subject'].death_date
1✔
254
            date_time = cleaned_data['date_time']
1✔
255
            if death_date and date_time and death_date < date_time.date():
1✔
256
                self.add_error('date_time', 'Date must be before subject death date.')
1✔
257
        return cleaned_data
1✔
258

259

260
class WaterAdministrationAdmin(BaseActionAdmin):
1✔
261
    form = WaterAdministrationForm
1✔
262

263
    fields = ['subject', 'date_time', 'water_administered', 'water_type', 'adlib', 'user',
1✔
264
              'session_l']
265
    list_display = ['subject_l', 'water_administered', 'user', 'date_time', 'water_type',
1✔
266
                    'adlib', 'session_l', 'projects']
267
    list_display_links = ('water_administered', )
1✔
268
    list_select_related = ('subject', 'user')
1✔
269
    ordering = ['-date_time', 'subject__nickname']
1✔
270
    search_fields = ['subject__nickname', 'subject__projects__name']
1✔
271
    list_filter = [ResponsibleUserListFilter, ('subject', RelatedDropdownFilter)]
1✔
272
    readonly_fields = ['session_l', ]
1✔
273

274
    def session_l(self, obj):
1✔
275
        url = get_admin_url(obj.session)
1✔
276
        return format_html('<a href="{url}">{session}</a>', session=obj.session or '-', url=url)
1✔
277
    session_l.short_description = 'Session'
1✔
278
    session_l.allow_tags = True
1✔
279

280

281
class WaterRestrictionForm(forms.ModelForm):
1✔
282

283
    class Meta:
1✔
284
        model = WaterRestriction
1✔
285
        fields = '__all__'
1✔
286

287

288
class WaterRestrictionAdmin(BaseActionAdmin):
1✔
289
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
1✔
290
        if db_field.name == 'subject':
1✔
291
            kwargs['queryset'] = Subject.objects.filter(cull__isnull=True).order_by('nickname')
1✔
292
            subject_id = self._get_last_subject(request)
1✔
293
            if subject_id:
1✔
294
                subject = Subject.objects.get(id=subject_id)
×
295
                kwargs['initial'] = subject
×
296
        return super(BaseActionAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1✔
297

298
    def get_form(self, request, obj=None, **kwargs):
1✔
299
        form = super(WaterRestrictionAdmin, self).get_form(request, obj, **kwargs)
1✔
300
        subject = getattr(obj, 'subject', None)
1✔
301
        rw = subject.water_control.weight() if subject else None
1✔
302
        if self.has_change_permission(request, obj):
1✔
303
            form.base_fields['reference_weight'].initial = rw or 0
1✔
304
        return form
1✔
305

306
    form = WaterRestrictionForm
1✔
307

308
    fields = ['subject', 'reference_weight', 'start_time',
1✔
309
              'end_time', 'water_type', 'users', 'narrative', 'implant_weight']
310
    list_display = ('subject_w', 'start_time_l', 'end_time_l', 'water_type', 'weight',
1✔
311
                    'weight_ref') + WaterControl._columns[3:] + ('projects',)
312
    list_select_related = ('subject',)
1✔
313
    list_display_links = ('start_time_l', 'end_time_l')
1✔
314
    readonly_fields = ('weight', 'implant_weight')  # WaterControl._columns[1:]
1✔
315
    ordering = ['-start_time', 'subject__nickname']
1✔
316
    search_fields = ['subject__nickname', 'subject__projects__name']
1✔
317
    list_filter = [ResponsibleUserListFilter,
1✔
318
                   ('subject', RelatedDropdownFilter),
319
                   ActiveFilter,
320
                   ]
321

322
    def subject_w(self, obj):
1✔
323
        url = reverse('water-history', kwargs={'subject_id': obj.subject.id})
1✔
324
        return format_html('<a href="{url}">{name}</a>', url=url, name=obj.subject.nickname)
1✔
325
    subject_w.short_description = 'subject'
1✔
326
    subject_w.admin_order_field = 'subject'
1✔
327

328
    def start_time_l(self, obj):
1✔
329
        return obj.start_time.date()
1✔
330
    start_time_l.short_description = 'start date'
1✔
331
    start_time_l.admin_order_field = 'start_time'
1✔
332

333
    def end_time_l(self, obj):
1✔
334
        if obj.end_time:
1✔
335
            return obj.end_time.date()
1✔
336
        else:
337
            return obj.end_time
1✔
338
    end_time_l.short_description = 'end date'
1✔
339
    end_time_l.admin_order_field = 'end_time'
1✔
340

341
    def weight(self, obj):
1✔
342
        if not obj.subject:
1✔
343
            return
×
344
        return '%.1f' % obj.subject.water_control.weight()
1✔
345
    weight.short_description = 'weight'
1✔
346

347
    def weight_ref(self, obj):
1✔
348
        if not obj.subject:
1✔
349
            return
×
350
        return '%.1f' % obj.subject.water_control.reference_weight()
1✔
351

352
    def expected_weight(self, obj):
1✔
353
        if not obj.subject:
1✔
354
            return
×
355
        return '%.1f' % obj.subject.water_control.expected_weight()
1✔
356
    expected_weight.short_description = 'weight exp'
1✔
357

358
    def percentage_weight(self, obj):
1✔
359
        if not obj.subject:
1✔
360
            return
×
361
        return '%.1f' % obj.subject.water_control.percentage_weight()
1✔
362
    percentage_weight.short_description = 'weight pct'
1✔
363

364
    def min_weight(self, obj):
1✔
365
        if not obj.subject:
1✔
366
            return
×
367
        return '%.1f' % obj.subject.water_control.min_weight()
1✔
368
    min_weight.short_description = 'weight min'
1✔
369

370
    def given_water_reward(self, obj):
1✔
371
        if not obj.subject:
1✔
372
            return
×
373
        return '%.2f' % obj.subject.water_control.given_water_reward()
1✔
374
    given_water_reward.short_description = 'water reward'
1✔
375

376
    def given_water_supplement(self, obj):
1✔
377
        if not obj.subject:
1✔
378
            return
×
379
        return '%.2f' % obj.subject.water_control.given_water_supplement()
1✔
380
    given_water_supplement.short_description = 'water suppl'
1✔
381

382
    def given_water_total(self, obj):
1✔
383
        if not obj.subject:
1✔
384
            return
×
385
        return '%.2f' % obj.subject.water_control.given_water_total()
1✔
386
    given_water_total.short_description = 'water tot'
1✔
387

388
    def implant_weight(self, obj):
1✔
389
        if not obj.subject:
1✔
390
            return
×
391
        return '%.2f' % (obj.subject.water_control.implant_weight() or 0.)
1✔
392
    implant_weight.short_description = 'implant weight'
1✔
393

394
    def has_change_permission(self, request, obj=None):
1✔
395
        # setting to override edition of water restrictions in the settings.lab file
396
        override = getattr(settings, 'WATER_RESTRICTIONS_EDITABLE', False)
1✔
397
        if override:
1✔
398
            return True
×
399
        else:
400
            return super(WaterRestrictionAdmin, self).has_change_permission(request, obj=obj)
1✔
401

402
    def expected_water(self, obj):
1✔
403
        if not obj.subject:
1✔
404
            return
×
405
        return '%.2f' % obj.subject.water_control.expected_water()
1✔
406
    expected_water.short_description = 'water exp'
1✔
407

408
    def excess_water(self, obj):
1✔
409
        if not obj.subject:
1✔
410
            return
×
411
        return '%.2f' % obj.subject.water_control.excess_water()
1✔
412
    excess_water.short_description = 'water excess'
1✔
413

414
    def is_water_restricted(self, obj):
1✔
415
        return obj.is_active()
1✔
416
    is_water_restricted.short_description = 'is active'
1✔
417
    is_water_restricted.boolean = True
1✔
418

419

420
class WeighingForm(BaseActionForm):
1✔
421
    def __init__(self, *args, **kwargs):
1✔
422
        super(WeighingForm, self).__init__(*args, **kwargs)
1✔
423
        if 'subject' in self.fields:
1✔
424
            # Order by alive subjects first
425
            ids = list(Subject
1✔
426
                       .objects
427
                       .filter(death_date__isnull=True)
428
                       .order_by('nickname')
429
                       .only('pk')
430
                       .values_list('pk', flat=True))
431
        # These ids first in the list of subjects, if any ids
432
        if ids:
1✔
433
            preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(ids)])
1✔
434
            subjects = Subject.objects.order_by(preserved, 'nickname')
1✔
435
        else:
436
            subjects = Subject.objects.order_by('nickname')
×
437
        self.fields['subject'].queryset = self.current_user.get_allowed_subjects(subjects)
1✔
438

439
        if self.fields.keys():
1✔
440
            self.fields['weight'].widget.attrs.update({'autofocus': 'autofocus'})
1✔
441

442

443
class WeighingAdmin(BaseActionAdmin):
1✔
444
    list_display = ['subject_l', 'weight', 'percentage_weight', 'date_time', 'projects']
1✔
445
    list_select_related = ('subject',)
1✔
446
    fields = ['subject', 'date_time', 'weight', 'user']
1✔
447
    ordering = ('-date_time',)
1✔
448
    list_display_links = ('weight',)
1✔
449
    search_fields = ['subject__nickname', 'subject__projects__name']
1✔
450
    list_filter = [ResponsibleUserListFilter,
1✔
451
                   ('subject', RelatedDropdownFilter)]
452

453
    form = WeighingForm
1✔
454

455
    def percentage_weight(self, obj):
1✔
456
        wc = obj.subject.water_control
1✔
457
        return wc.percentage_weight_html(date=obj.date_time)
1✔
458
    percentage_weight.short_description = 'Weight %'
1✔
459

460

461
class WaterTypeAdmin(BaseActionAdmin):
1✔
462
    list_display = ['name', 'json']
1✔
463
    fields = ['name', 'json']
1✔
464
    ordering = ('name',)
1✔
465
    list_display_links = ('name',)
1✔
466

467

468
class SurgeryAdmin(BaseActionAdmin):
1✔
469
    form = BaseActionForm
1✔
470
    list_display = ['subject_l', 'date', 'users_l', 'procedures_l',
1✔
471
                    'narrative', 'projects', 'implant_weight']
472
    list_select_related = ('subject',)
1✔
473

474
    fields = BaseActionAdmin.fields + ['outcome_type', 'implant_weight']
1✔
475
    list_display_links = ['date']
1✔
476
    search_fields = ('subject__nickname', 'subject__projects__name')
1✔
477
    list_filter = [SubjectAliveListFilter,
1✔
478
                   ResponsibleUserListFilter,
479
                   ('subject__line', RelatedDropdownFilter),
480
                   ]
481
    ordering = ['-start_time']
1✔
482
    inlines = [NoteInline]
1✔
483

484
    def date(self, obj):
1✔
485
        return obj.start_time.date()
1✔
486
    date.admin_order_field = 'start_time'
1✔
487

488
    def users_l(self, obj):
1✔
489
        return ', '.join(map(str, obj.users.all()))
1✔
490
    users_l.short_description = 'users'
1✔
491

492
    def procedures_l(self, obj):
1✔
493
        return ', '.join(map(str, obj.procedures.all()))
1✔
494
    procedures_l.short_description = 'procedures'
1✔
495

496
    def get_queryset(self, request):
1✔
497
        return super(SurgeryAdmin, self).get_queryset(request).prefetch_related(
1✔
498
            'users', 'procedures')
499

500

501
class DatasetInline(BaseInlineAdmin):
1✔
502
    show_change_link = True
1✔
503
    model = Dataset
1✔
504
    extra = 1
1✔
505
    fields = ('name', 'dataset_type', 'collection', '_online', 'version', 'qc',
1✔
506
              'created_by', 'created_datetime')
507
    readonly_fields = fields
1✔
508
    ordering = ('name',)
1✔
509

510
    def _online(self, obj):
1✔
511
        return obj.is_online
1✔
512
    _online.short_description = 'On server'
1✔
513
    _online.boolean = True
1✔
514

515

516
class WaterAdminInline(BaseInlineAdmin):
1✔
517
    model = WaterAdministration
1✔
518
    extra = 0
1✔
519
    fields = ('name', 'water_administered', 'water_type')
1✔
520
    readonly_fields = ('name', 'water_administered', 'water_type')
1✔
521

522

523
class TasksAdminInline(BaseInlineAdmin):
1✔
524
    model = Task
1✔
525
    extra = 0
1✔
526
    fields = ('status', 'name', 'version', 'parents', 'datetime', 'arguments')
1✔
527
    readonly_fields = ('name', 'version', 'parents', 'datetime', 'arguments')
1✔
528
    ordering = ('status',)
1✔
529

530

531
def _pass_narrative_templates(context):
1✔
532
    context['narrative_templates'] = \
1✔
533
        base64.b64encode(json.dumps(settings.NARRATIVE_TEMPLATES).encode('utf-8')).decode('utf-8')
534
    return context
1✔
535

536

537
class SessionAdmin(BaseActionAdmin):
1✔
538
    list_display = ['subject_l', 'start_time', 'number', 'lab', 'dataset_count',
1✔
539
                    'task_protocol', 'qc', 'user_list', 'project_']
540
    list_display_links = ['start_time']
1✔
541
    fields = BaseActionAdmin.fields + [
1✔
542
        'repo_url', 'qc', 'extended_qc', 'projects', ('type', 'task_protocol', ), 'number',
543
        'n_correct_trials', 'n_trials', 'weighing', 'auto_datetime']
544
    list_filter = [('users', RelatedDropdownFilter),
1✔
545
                   ('start_time', DateRangeFilter),
546
                   ('projects', RelatedDropdownFilter),
547
                   ('lab', RelatedDropdownFilter),
548
                   ]
549
    search_fields = ('subject__nickname', 'lab__name', 'projects__name', 'users__username',
1✔
550
                     'task_protocol', 'pk')
551
    ordering = ('-start_time', 'task_protocol', 'lab')
1✔
552
    inlines = [WaterAdminInline, TasksAdminInline, DatasetInline, NoteInline]
1✔
553
    readonly_fields = ['repo_url', 'task_protocol', 'weighing', 'qc', 'extended_qc',
1✔
554
                       'auto_datetime']
555

556
    def get_form(self, request, obj=None, **kwargs):
1✔
557
        from subjects.admin import Project
1✔
558
        from django.db.models import Q
1✔
559
        form = super(SessionAdmin, self).get_form(request, obj, **kwargs)
1✔
560
        if form.base_fields and not request.user.is_superuser:
1✔
561
            # the projects edit box is limited to projects with no user or containing current user
562
            current_proj = obj.projects.all() if obj else None
×
563
            form.base_fields['projects'].queryset = Project.objects.filter(
×
564
                Q(users=request.user.pk) | Q(users=None) | Q(pk__in=current_proj)
565
            ).distinct()
566
        return form
1✔
567

568
    def change_view(self, request, object_id, extra_context=None, **kwargs):
1✔
569
        context = extra_context or {}
1✔
570
        context = _pass_narrative_templates(context)
1✔
571
        return super(SessionAdmin, self).change_view(
1✔
572
            request, object_id, extra_context=context, **kwargs)
573

574
    def add_view(self, request, extra_context=None):
1✔
575
        context = extra_context or {}
1✔
576
        context = _pass_narrative_templates(context)
1✔
577
        return super(SessionAdmin, self).add_view(request, extra_context=context)
1✔
578

579
    def project_(self, obj):
1✔
580
        return [getattr(p, 'name', None) for p in obj.projects.all()]
1✔
581

582
    def repo_url(self, obj):
1✔
583
        url = settings.SESSION_REPO_URL.format(
1✔
584
            lab=obj.subject.lab.name,
585
            subject=obj.subject.nickname,
586
            date=obj.start_time.date(),
587
            number=obj.number or 0,
588
        )
589
        return format_html(
1✔
590
            '<a href="{url}">{url}</a>'.format(url=url))
591

592
    def user_list(self, obj):
1✔
593
        return ', '.join(map(str, obj.users.all()))
1✔
594
    user_list.short_description = 'users'
1✔
595

596
    def dataset_count(self, ses):
1✔
597
        cs = FileRecord.objects.filter(dataset__in=ses.data_dataset_session_related.all(),
1✔
598
                                       data_repository__globus_is_personal=False,
599
                                       exists=True).values_list('relative_path').distinct().count()
600
        cr = FileRecord.objects.filter(dataset__in=ses.data_dataset_session_related.all(),
1✔
601
                                       ).values_list('relative_path').distinct().count()
602
        if cr == 0:
1✔
603
            return '-'
1✔
604
        col = '008000' if cr == cs else '808080'  # green if all files uploaded on server
×
605
        return format_html('<b><a style="color: #{};">{}</a></b>', col, '{:2.0f}'.format(cr))
×
606
    dataset_count.short_description = '# datasets'
1✔
607

608
    def weighing(self, obj):
1✔
609
        wei = Weighing.objects.filter(date_time=obj.start_time)
1✔
610
        if not wei:
1✔
611
            return ''
1✔
612
        url = reverse('admin:%s_%s_change' % (wei[0]._meta.app_label, wei[0]._meta.model_name),
×
613
                      args=[wei[0].id])
614
        return format_html('<b><a href="{url}" ">{} g </a></b>', wei[0].weight, url=url)
×
615
    weighing.short_description = 'weight before session'
1✔
616

617

618
class ProbeInsertionInline(TabularInline):
1✔
619
    fk_name = "session"
1✔
620
    show_change_link = True
1✔
621
    model = ProbeInsertion
1✔
622
    fields = ('name', 'model')
1✔
623
    extra = 0
1✔
624

625

626
class FOVInline(TabularInline):
1✔
627
    fk_name = 'session'
1✔
628
    show_change_link = True
1✔
629
    model = FOV
1✔
630
    fields = ('name', 'imaging_type')
1✔
631
    extra = 0
1✔
632

633

634
class EphysSessionAdmin(SessionAdmin):
1✔
635
    inlines = [ProbeInsertionInline, TasksAdminInline, WaterAdminInline, DatasetInline, NoteInline]
1✔
636

637
    def get_queryset(self, request):
1✔
638
        qs = super(EphysSessionAdmin, self).get_queryset(request)
1✔
639
        return qs.filter(procedures__name__icontains='ephys')
1✔
640

641

642
class ImagingSessionAdmin(SessionAdmin):
1✔
643
    inlines = [FOVInline, TasksAdminInline, WaterAdminInline, DatasetInline, NoteInline]
1✔
644
    list_filter = [('users', RelatedDropdownFilter),
1✔
645
                   ('start_time', DateRangeFilter),
646
                   ('projects', RelatedDropdownFilter),
647
                   ('lab', RelatedDropdownFilter),
648
                   ('field_of_view__imaging_type', RelatedDropdownFilter)]
649

650
    def get_queryset(self, request):
1✔
651
        qs = super(ImagingSessionAdmin, self).get_queryset(request)
1✔
652
        return qs.filter(procedures__name__icontains='imaging')
1✔
653

654

655
class NotificationUserFilter(DefaultListFilter):
1✔
656
    title = 'notification users'
1✔
657
    parameter_name = 'users'
1✔
658

659
    def lookups(self, request, model_admin):
1✔
660
        return (
1✔
661
            (None, 'Me'),
662
            ('all', 'All'),
663
        )
664

665
    def queryset(self, request, queryset):
1✔
666
        if self.value() is None:
1✔
667
            return queryset.filter(users__in=[request.user])
1✔
668
        elif self.value == 'all':
×
669
            return queryset.all()
×
670

671

672
class NotificationAdmin(BaseAdmin):
1✔
673
    list_display = ('title', 'subject', 'users_l',
1✔
674
                    'send_at', 'sent_at',
675
                    'status', 'notification_type')
676
    search_fields = ('notification_type', 'subject__nickname', 'title')
1✔
677
    list_filter = (NotificationUserFilter, 'notification_type')
1✔
678
    fields = ('title', 'notification_type', 'subject', 'message',
1✔
679
              'users', 'status', 'send_at', 'sent_at')
680
    ordering = ('-send_at',)
1✔
681

682
    def users_l(self, obj):
1✔
683
        return sorted(map(str, obj.users.all()))
×
684

685

686
class NotificationRuleAdmin(BaseAdmin):
1✔
687
    list_display = ('notification_type', 'user', 'subjects_scope')
1✔
688
    search_fields = ('notification_type', 'user__username', 'subject_scope')
1✔
689
    fields = ('notification_type', 'user', 'subjects_scope')
1✔
690

691
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
1✔
692
        if db_field.name == 'user':
1✔
693
            kwargs['initial'] = request.user.id
1✔
694
        return super(NotificationRuleAdmin, self).formfield_for_foreignkey(
1✔
695
            db_field, request, **kwargs
696
        )
697

698

699
class CullAdmin(BaseAdmin):
1✔
700
    list_display = ('date', 'subject_l', 'user', 'cull_reason', 'cull_method', 'projects')
1✔
701
    search_fields = ('user__username', 'subject__nickname', 'subject__projects__name')
1✔
702
    fields = ('date', 'subject', 'user', 'cull_reason', 'cull_method', 'description')
1✔
703
    ordering = ('-date',)
1✔
704

705
    def subject_l(self, obj):
1✔
706
        url = get_admin_url(obj.subject)
×
707
        return format_html('<a href="{url}">{subject}</a>', subject=obj.subject or '-', url=url)
×
708
    subject_l.short_description = 'subject'
1✔
709

710
    def projects(self, obj):
1✔
711
        return ', '.join(p.name for p in obj.subject.projects.all())
×
712

713

714
admin.site.register(ProcedureType, ProcedureTypeAdmin)
1✔
715
admin.site.register(Weighing, WeighingAdmin)
1✔
716
admin.site.register(WaterAdministration, WaterAdministrationAdmin)
1✔
717
admin.site.register(WaterRestriction, WaterRestrictionAdmin)
1✔
718

719
admin.site.register(Session, SessionAdmin)
1✔
720
admin.site.register(EphysSession, EphysSessionAdmin)
1✔
721
admin.site.register(ImagingSession, ImagingSessionAdmin)
1✔
722
admin.site.register(OtherAction, OtherActionAdmin)
1✔
723
admin.site.register(VirusInjection, BaseActionAdmin)
1✔
724

725
admin.site.register(Surgery, SurgeryAdmin)
1✔
726
admin.site.register(WaterType, WaterTypeAdmin)
1✔
727

728
admin.site.register(Notification, NotificationAdmin)
1✔
729
admin.site.register(NotificationRule, NotificationRuleAdmin)
1✔
730

731
admin.site.register(Cull, CullAdmin)
1✔
732
admin.site.register(CullReason, BaseAdmin)
1✔
733
admin.site.register(CullMethod, BaseAdmin)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc