Skip to content

Commit e845e17

Browse files
committed
Better implementation of timezone
1 parent 3d4dcf0 commit e845e17

File tree

8 files changed

+100
-22
lines changed

8 files changed

+100
-22
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import logging
2+
3+
from one.registration import get_dataset_type, Bunch
4+
5+
from django.core.management import BaseCommand
6+
7+
from data.models import Dataset, DatasetType
8+
logging.getLogger(__name__).setLevel(logging.WARNING)
9+
10+
11+
class Command(BaseCommand):
12+
"""
13+
./manage.py check_dataset_type --pattern '_*_object.attribute.*'
14+
./manage.py check_dataset_type --pattern '_*_object.attribute.*' --filename '_m1_object.attribute.json'
15+
./manage.py check_dataset_type --name 'object.attribute' --filename '_m1_object.attribute.json'
16+
./manage.py check_dataset_type --pattern '_*_object.attribute.*' --strict
17+
"""
18+
help = "Check dataset type patterns against existing datasets."
19+
20+
def add_arguments(self, parser):
21+
parser.add_argument('--name', help='Dataset type name', default='__new_dataset_type__')
22+
parser.add_argument('--pattern', help='Pattern to match dataset types', type=str)
23+
parser.add_argument('--filename', help='A filename that should match the pattern')
24+
parser.add_argument(
25+
'--strict', help='Whether to check other dataset types strictly', action='store_true')
26+
27+
def handle(self, *args, **options):
28+
name = options.get('name')
29+
pattern = options.get('pattern')
30+
filename = options.get('filename')
31+
# Fetch one dataset for each dataset type
32+
self.stdout.write('Fetching one dataset for each dataset type...')
33+
dsets = (
34+
Dataset.objects
35+
.order_by('dataset_type', '-auto_datetime')
36+
.distinct('dataset_type')
37+
.values_list('name', 'dataset_type')
38+
)
39+
dtypes = list(map(Bunch, DatasetType.objects.values('id', 'name', 'filename_pattern')))
40+
assert name not in [d['name'] for d in dtypes], \
41+
f'Dataset type name {name} already exists.'
42+
assert pattern not in [d['filename_pattern'] for d in dtypes], \
43+
f'Pattern {pattern} already exists.'
44+
dtypes.append(Bunch({'id': name, 'name': name, 'filename_pattern': pattern}))
45+
# If example filename provided, check it against all patterns
46+
if filename:
47+
self.stdout.write(f'Checking filename "{filename}" against all patterns...')
48+
dtype = get_dataset_type(filename, dtypes)
49+
assert dtype == dtypes[-1], (
50+
f'Filename "{filename}" did not match the expected pattern "{pattern}". '
51+
f'Got dataset type: {dtype}'
52+
)
53+
54+
# Check dataset types for existing datasets
55+
for dset, expected_dtype in dsets:
56+
try:
57+
dtype = get_dataset_type(dset, dtypes)
58+
except ValueError as e:
59+
if not options.get('strict') and 'No dataset type found' in str(e):
60+
dtype = DatasetType.objects.get(id=expected_dtype)
61+
self.stdout.write(
62+
self.style.WARNING(
63+
f'Skipping dataset "{dset}" as no matching dataset type found '
64+
f'(expected {dtype.name} {expected_dtype}).'
65+
)
66+
)
67+
continue
68+
else:
69+
raise
70+
assert dtype['id'] == expected_dtype, (
71+
f'Dataset type mismatch for dataset: {dset}. '
72+
f'Expected: {expected_dtype}, Found: {dtype["id"]}'
73+
)
74+
75+
self.stdout.write(self.style.SUCCESS('All dataset type patterns appear valid.'))

alyx/data/management/one_django.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/home/ters0n/PYTHON/iblalyx/management/one_django.py

alyx/data/migrations/0022_alter_datarepository_timezone.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
# Generated by Django 5.2.8 on 2025-11-05 23:51
1+
# Generated by Django 5.2.8 on 2025-11-07 17:44
22

3+
import data.models
34
from django.db import migrations, models
45

56

@@ -13,6 +14,6 @@ class Migration(migrations.Migration):
1314
migrations.AlterField(
1415
model_name='datarepository',
1516
name='timezone',
16-
field=models.CharField(blank=True, default='Africa/Djibouti', help_text='Timezone of the server (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)', max_length=64),
17+
field=models.CharField(blank=True, default=data.models.default_timezone, help_text='Timezone of the server (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)', max_length=64),
1718
),
1819
]

alyx/data/models.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
from django.core.validators import RegexValidator
55
from django.db import models
6+
from django.conf import settings
67
from django.utils import timezone
78
from django.contrib.contenttypes.fields import GenericForeignKey
89
from django.contrib.contenttypes.models import ContentType
910

10-
from alyx.settings import TIME_ZONE, AUTH_USER_MODEL
1111
from actions.models import Session
1212
from alyx.base import BaseModel, modify_fields, BaseManager, CharNullField, BaseQuerySet, ALF_SPEC
1313

@@ -18,6 +18,10 @@ def _related_string(field):
1818
return "%(app_label)s_%(class)s_" + field + "_related"
1919

2020

21+
def default_timezone():
22+
return settings.TIME_ZONE
23+
24+
2125
# Data repositories
2226
# ------------------------------------------------------------------------------------------------
2327

@@ -67,7 +71,7 @@ class DataRepository(BaseModel):
6771
blank=True, null=True,
6872
help_text="URL of the data repository, if it is accessible via HTTP")
6973
timezone = models.CharField(
70-
max_length=64, blank=True, default=TIME_ZONE,
74+
max_length=64, blank=True, default=default_timezone,
7175
help_text="Timezone of the server "
7276
"(see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)")
7377
globus_path = models.CharField(
@@ -147,7 +151,7 @@ class DatasetType(BaseModel):
147151
help_text="Short identifying nickname, e.g. 'spikes.times'")
148152

149153
created_by = models.ForeignKey(
150-
AUTH_USER_MODEL, blank=True, null=True,
154+
settings.AUTH_USER_MODEL, blank=True, null=True,
151155
on_delete=models.CASCADE,
152156
related_name=_related_string('created_by'),
153157
help_text="The creator of the data.")
@@ -194,7 +198,7 @@ class BaseExperimentalData(BaseModel):
194198
help_text="The Session to which this data belongs")
195199

196200
created_by = models.ForeignKey(
197-
AUTH_USER_MODEL, blank=True, null=True,
201+
settings.AUTH_USER_MODEL, blank=True, null=True,
198202
on_delete=models.CASCADE,
199203
related_name=_related_string('created_by'),
200204
help_text="The creator of the data.")
@@ -470,7 +474,7 @@ def __str__(self):
470474
# ------------------------------------------------------------------------------------------------
471475

472476
class Download(BaseModel):
473-
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE)
477+
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
474478
dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE)
475479
first_download = models.DateTimeField(auto_now_add=True)
476480
last_download = models.DateTimeField(auto_now=True)

alyx/misc/admin.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from django import forms
44
from django.db import models
55
from django.db.models import Q
6-
from django.conf import settings
76
from django.contrib import admin
87
from django.contrib.admin.widgets import AdminFileWidget
98
from django.contrib.contenttypes.admin import GenericTabularInline
@@ -71,12 +70,6 @@ def local(self, obj):
7170
def server(self, obj):
7271
return ','.join([p.name for p in obj.repositories.filter(globus_is_personal=False)])
7372

74-
def get_form(self, request, obj=None, **kwargs):
75-
form = super().get_form(request, obj, **kwargs)
76-
if not obj:
77-
form.base_fields['timezone'].initial = settings.TIME_ZONE
78-
return form
79-
8073

8174
class LabMembershipAdmin(BaseAdmin):
8275
fields = ['user', 'lab', 'role', 'start_date', 'end_date']

alyx/misc/migrations/0012_alter_lab_timezone.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
# Generated by Django 5.2.8 on 2025-11-05 23:51
1+
# Generated by Django 5.2.8 on 2025-11-07 17:44
22

3+
import misc.models
34
from django.db import migrations, models
45

56

@@ -13,6 +14,6 @@ class Migration(migrations.Migration):
1314
migrations.AlterField(
1415
model_name='lab',
1516
name='timezone',
16-
field=models.CharField(blank=True, help_text='Timezone of the server (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)', max_length=64),
17+
field=models.CharField(blank=True, default=misc.models.default_timezone, help_text='Timezone of the server (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)', max_length=64),
1718
),
1819
]

alyx/misc/models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ def default_lab():
2525
return settings.DEFAULT_LAB_PK
2626

2727

28+
def default_timezone():
29+
return settings.TIME_ZONE
30+
31+
2832
class LabMember(AbstractUser):
2933
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
3034
is_stock_manager = models.BooleanField(default=False)
@@ -77,7 +81,7 @@ class Lab(BaseModel):
7781
institution = models.CharField(max_length=255, blank=True)
7882
address = models.CharField(max_length=255, blank=True)
7983
timezone = models.CharField(
80-
max_length=64, blank=True,
84+
max_length=64, blank=True, default=default_timezone,
8185
help_text="Timezone of the server "
8286
"(see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)")
8387

@@ -105,10 +109,6 @@ class Lab(BaseModel):
105109
def __str__(self):
106110
return self.name
107111

108-
def save(self, *args, timezone=None, **kwargs):
109-
self.timezone = timezone or self.timezone or settings.TIME_ZONE
110-
super().save(*args, **kwargs)
111-
112112

113113
class LabMembership(BaseModel):
114114
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

alyx/misc/tests_admin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ def test_admin_add_form_view(self):
3636
LabForm = la.get_form(request)
3737
# Check initial value of timezone field
3838
form = LabForm(data={})
39-
self.assertEqual(form.fields['timezone'].initial, 'Africa/Djibouti')
39+
initial = form.fields['timezone'].initial
40+
if callable(initial):
41+
initial = initial()
42+
self.assertEqual(initial, 'Africa/Djibouti')
4043
# Check form cleaning
4144
data = {
4245
'timezone': 'Africa/Djibouti',

0 commit comments

Comments
 (0)