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

cortex-lab / alyx / 12373047435

17 Dec 2024 12:30PM UTC coverage: 83.706% (+0.004%) from 83.702%
12373047435

push

github

oliche
update digitalInputs dataset type

7639 of 9126 relevant lines covered (83.71%)

0.84 hits per line

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

81.42
alyx/misc/admin.py
1
from pytz import all_timezones
1✔
2

3
from django import forms
1✔
4
from django.db import models
1✔
5
from django.db.models import Q
1✔
6
from django.contrib import admin
1✔
7
from django.contrib.admin.widgets import AdminFileWidget
1✔
8
from django.contrib.contenttypes.admin import GenericTabularInline
1✔
9
from django.contrib.postgres.fields import JSONField
1✔
10
from django.utils.html import format_html, format_html_join
1✔
11
from django.utils.safestring import mark_safe
1✔
12
from rest_framework.authtoken.models import TokenProxy
1✔
13

14
from misc.models import Note, Lab, LabMembership, LabLocation, CageType, \
1✔
15
    Enrichment, Food, Housing, HousingSubject
16
from alyx.base import BaseAdmin, DefaultListFilter, get_admin_url
1✔
17

18

19
class LabForm(forms.ModelForm):
1✔
20
    def __init__(self, *args, **kwargs):
1✔
21
        super(LabForm, self).__init__(*args, **kwargs)
1✔
22
        # if user has read-only permissions only fields is empty
23
        if not self.is_bound:
1✔
24
            return
1✔
25
        self.fields['reference_weight_pct'].help_text =\
×
26
            'Threshold ratio triggers a warning using the Reference Weight method (0-1)'
27
        self.fields['reference_weight_pct'].label = 'Reference Weight Ratio'
×
28
        self.fields['zscore_weight_pct'].help_text =\
×
29
            'Threshold ratio triggers a warning is raised using the Z-Score method (0-1)'
30
        self.fields['zscore_weight_pct'].label = 'Z-score Weight Ratio'
×
31

32
    def clean_reference_weight_pct(self):
1✔
33
        ref = self.cleaned_data['reference_weight_pct']
×
34
        ref = max(ref, 0)
×
35
        if ref > 1:
×
36
            ref = ref / 100
×
37
        return ref
×
38

39
    def clean_zscore_weight_pct(self):
1✔
40
        ref = self.cleaned_data['zscore_weight_pct']
×
41
        ref = max(ref, 0)
×
42
        if ref > 1:
×
43
            ref = ref / 100
×
44
        return ref
×
45

46
    def clean_timezone(self):
1✔
47
        ref = self.cleaned_data['timezone']
×
48
        if ref not in all_timezones:
×
49
            raise forms.ValidationError(
×
50
                ("Time Zone is incorrect here is the list (column TZ Database Name):  "
51
                 "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"))
52
        return ref
×
53

54

55
class LabAdmin(BaseAdmin):
1✔
56
    form = LabForm
1✔
57
    generics = ['name', 'institution', 'address', 'timezone',
1✔
58
                'reference_weight_pct', 'zscore_weight_pct']
59
    list_display = ['name', 'institution', 'address', 'timezone', 'local', 'server',
1✔
60
                    'reference_weight_pct', 'zscore_weight_pct']
61
    list_select_related = ['cage_type', 'enrichment', 'food']
1✔
62
    fields = generics + list_select_related + ['cage_cleaning_frequency_days', 'light_cycle',
1✔
63
                                               'repositories']
64

65
    def local(self, obj):
1✔
66
        return ','.join([p.name for p in obj.repositories.filter(globus_is_personal=True)])
1✔
67

68
    def server(self, obj):
1✔
69
        return ','.join([p.name for p in obj.repositories.filter(globus_is_personal=False)])
1✔
70

71

72
class LabMembershipAdmin(BaseAdmin):
1✔
73
    fields = ['user', 'lab', 'role', 'start_date', 'end_date']
1✔
74
    list_display = fields
1✔
75

76

77
class LabLocationAdmin(BaseAdmin):
1✔
78
    fields = ['name', 'lab']
1✔
79
    list_display = fields
1✔
80
    search_fields = ('lab__name', 'name',)
1✔
81
    ordering = ('lab__name', 'name',)
1✔
82

83

84
class AdminImageWidget(AdminFileWidget):
1✔
85

86
    def render(self, name, value, attrs=None, renderer=None):
1✔
87
        output = []
1✔
88
        if value and getattr(value, "url", None):
1✔
89
            image_url = value.url
×
90
            file_name = str(value)
×
91
            output.append(f'<a href="{image_url}" target="_blank">'
×
92
                          f'<img src="{image_url}" width="400" alt="{file_name}" /></a><br>')
93
        output.append(super(AdminFileWidget, self).render(name, value, attrs, renderer))
1✔
94
        return mark_safe(''.join(output))
1✔
95

96

97
class ImageWidgetAdmin(BaseAdmin):
1✔
98
    image_fields = []
1✔
99

100
    def formfield_for_dbfield(self, db_field, **kwargs):
1✔
101
        if db_field.name in self.image_fields:
1✔
102
            request = kwargs.pop("request", None)  # noqa
1✔
103
            kwargs['widget'] = AdminImageWidget
1✔
104
            return db_field.formfield(**kwargs)
1✔
105
        return super(ImageWidgetAdmin, self).formfield_for_dbfield(db_field, **kwargs)
1✔
106

107

108
class NoteAdmin(ImageWidgetAdmin):
1✔
109
    list_display = ['user', 'date_time', 'content_object', 'text', 'image']
1✔
110
    list_display_links = ['date_time']
1✔
111
    image_fields = ['image']
1✔
112
    fields = ['user', 'date_time', 'text', 'image', 'content_type', 'object_id']
1✔
113
    ordering = ('-date_time',)
1✔
114
    search_fields = ['text']
1✔
115

116

117
class NoteInline(GenericTabularInline):
1✔
118
    model = Note
1✔
119
    extra = 1
1✔
120
    fields = ('user', 'date_time', 'text', 'image')
1✔
121
    image_fields = ('image',)
1✔
122
    ordering = ('-date_time',)
1✔
123

124
    formfield_overrides = {
1✔
125
        models.TextField: {'widget': forms.Textarea(
126
                           attrs={'rows': 3,
127
                                  'cols': 30})},
128
        JSONField: {'widget': forms.Textarea(
129
                    attrs={'rows': 3,
130
                           'cols': 30})},
131
        models.CharField: {'widget': forms.TextInput(attrs={'size': 16})},
132
    }
133

134
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
1✔
135
        # Logged-in user by default.
136
        if db_field.name == 'user':
1✔
137
            kwargs['initial'] = request.user
1✔
138
        return super(NoteInline, self).formfield_for_foreignkey(
1✔
139
            db_field, request, **kwargs
140
        )
141

142
    def formfield_for_dbfield(self, db_field, **kwargs):
1✔
143
        if db_field.name in self.image_fields:
1✔
144
            request = kwargs.pop("request", None)  # noqa
1✔
145
            kwargs['widget'] = AdminImageWidget
1✔
146
            return db_field.formfield(**kwargs)
1✔
147
        return super(NoteInline, self).formfield_for_dbfield(db_field, **kwargs)
1✔
148

149
    def has_delete_permission(self, request, obj=None):
1✔
150
        return False
1✔
151

152

153
class CageTypeAdmin(BaseAdmin):
1✔
154
    fields = ('name', 'description',)
1✔
155
    list_display = fields
1✔
156
    search_fields = ('name',)
1✔
157

158

159
class EnrichmentAdmin(BaseAdmin):
1✔
160
    fields = ('name', 'description',)
1✔
161
    list_display = fields
1✔
162
    search_fields = ('name',)
1✔
163

164

165
class FoodAdmin(BaseAdmin):
1✔
166
    fields = ('name', 'description',)
1✔
167
    list_display = fields
1✔
168
    search_fields = ('name',)
1✔
169

170

171
class HousingSubjectAdminInline(admin.TabularInline):
1✔
172
    model = HousingSubject
1✔
173
    extra = 1
1✔
174
    fields = ('subject', 'start_datetime', 'end_datetime')
1✔
175

176
    def get_queryset(self, request):
1✔
177
        qs = super(HousingSubjectAdminInline, self).get_queryset(request)
1✔
178
        return qs.filter(subject__cull__isnull=True)
1✔
179

180

181
class HousingAdminForm(forms.ModelForm):
1✔
182

183
    def __init__(self, *args, **kwargs):
1✔
184
        super().__init__(*args, **kwargs)
1✔
185
        request = self.Meta.formfield_callback.keywords['request']
1✔
186
        self.user = request.user
1✔
187
        lab = self.user.lab_id()
1✔
188
        if lab:
1✔
189
            self.fields['cage_type'].initial = lab[0].cage_type
×
190
            self.fields['enrichment'].initial = lab[0].enrichment
×
191
            self.fields['light_cycle'].initial = lab[0].light_cycle
×
192
            self.fields['food'].initial = lab[0].food
×
193
            self.fields['cage_cleaning_frequency_days'].initial =\
×
194
                lab[0].cage_cleaning_frequency_days
195

196
    class Meta():
1✔
197
        model = Housing
1✔
198
        fields = ['cage_type', 'cage_name',
1✔
199
                  'enrichment', 'light_cycle',
200
                  'food', 'cage_cleaning_frequency_days']
201

202

203
class HousingIsCurrentFilter(DefaultListFilter):
1✔
204
    title = 'Housing Current'
1✔
205
    parameter_name = 'Housing Current'
1✔
206

207
    def lookups(self, request, model_admin):
1✔
208
        return (
1✔
209
            (None, 'Current'),
210
            ('All', 'All'),
211
        )
212

213
    def queryset(self, request, queryset):
1✔
214
        if self.value() is None:
1✔
215
            return queryset.exclude(nsubs=0)
1✔
216

217

218
class HousingAdmin(BaseAdmin):
1✔
219

220
    inlines = [HousingSubjectAdminInline]
1✔
221
    form = HousingAdminForm
1✔
222

223
    fields = ['subjects_l',
1✔
224
              ('cage_type', 'cage_name'),
225
              ('enrichment', 'light_cycle',),
226
              ('food', 'cage_cleaning_frequency_days')]
227
    search_fields = ('housing_subjects__subject__nickname', 'housing_subjects__subject__lab__name')
1✔
228
    list_display = ('cage_l', 'subjects_l', 'subjects_old', 'start',
1✔
229
                    'end', 'subjects_count', 'lab',)
230
    readonly_fields = ('subjects_l',)
1✔
231
    list_filter = (HousingIsCurrentFilter,)
1✔
232

233
    def get_queryset(self, request):
1✔
234
        qs = Housing.objects.annotate(
1✔
235
            nsubs=models.Count('housing_subjects',
236
                               filter=Q(housing_subjects__end_datetime__isnull=True),
237
                               distinct=True))
238
        qs = qs.annotate(start_datetime=models.Min('housing_subjects__start_datetime'))
1✔
239
        qs = qs.annotate(end_datetime=models.Case(models.When(
1✔
240
            nsubs=0, then=models.Max('housing_subjects__end_datetime')),
241
            output_field=models.DateTimeField(),))
242
        return qs
1✔
243

244
    def start(self, obj):
1✔
245
        return obj.start_datetime
×
246

247
    def end(self, obj):
1✔
248
        return obj.end_datetime
×
249

250
    def subjects_count(self, obj):
1✔
251
        return obj.nsubs
×
252
    subjects_count.short_description = '# active'
1✔
253

254
    def cage_l(self, obj):
1✔
255
        return format_html('<a href="{url}">{hou}</a>', url=get_admin_url(obj), hou=obj.cage_name)
×
256
    cage_l.short_description = 'cage'
1✔
257

258
    def subjects_l(self, obj):
1✔
259
        out = format_html_join(', ', '<a href="{}">{}</a>',
1✔
260
                               ((get_admin_url(sub), sub) for sub in obj.subjects_current()))
261
        return format_html(out)
×
262
    subjects_l.short_description = 'subjects'
1✔
263

264
    def subjects_old(self, obj):
1✔
265
        subs = obj.subjects.exclude(pk__in=obj.subjects_current().values_list('pk', flat=True))
×
266
        out = format_html_join(', ', '<a href="{}">{}</a>',
×
267
                               ((get_admin_url(sub), sub) for sub in subs))
268
        return format_html(out)
×
269
    subjects_old.short_description = 'old subjects'
1✔
270

271

272
admin.site.register(Housing, HousingAdmin)
1✔
273
admin.site.register(Lab, LabAdmin)
1✔
274
admin.site.register(LabMembership, LabMembershipAdmin)
1✔
275
admin.site.register(LabLocation, LabLocationAdmin)
1✔
276
admin.site.register(Note, NoteAdmin)
1✔
277
admin.site.register(CageType, CageTypeAdmin)
1✔
278
admin.site.register(Enrichment, EnrichmentAdmin)
1✔
279
admin.site.register(Food, FoodAdmin)
1✔
280
admin.site.unregister(TokenProxy)
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