Skip to content

Commit 0a364bb

Browse files
committed
feat: add deployment time zone metadata
1 parent 0b6eb9c commit 0a364bb

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed

ami/main/api/serializers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ class Meta:
185185
"updated_at",
186186
"latitude",
187187
"longitude",
188+
"time_zone",
188189
"first_date",
189190
"last_date",
190191
"device",
@@ -234,6 +235,7 @@ class Meta:
234235
"id",
235236
"name",
236237
"details",
238+
"time_zone",
237239
]
238240

239241

@@ -247,6 +249,7 @@ class Meta:
247249
"details",
248250
"latitude",
249251
"longitude",
252+
"time_zone",
250253
"events_count",
251254
# "captures_count",
252255
# "detections_count",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from django.conf import settings
2+
from django.db import migrations, models
3+
4+
5+
class Migration(migrations.Migration):
6+
7+
dependencies = [
8+
("main", "0078_classification_applied_to"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="deployment",
14+
name="time_zone",
15+
field=models.CharField(
16+
default=settings.TIME_ZONE,
17+
help_text="IANA time zone for this deployment (e.g., 'America/Los_Angeles'). Used as metadata for interpreting local timestamps.",
18+
max_length=64,
19+
),
20+
),
21+
]

ami/main/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import urllib.parse
99
from io import BytesIO
1010
from typing import Final, final # noqa: F401
11+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
1112

1213
import PIL.Image
1314
import pydantic
@@ -56,6 +57,15 @@
5657
_POST_TITLE_MAX_LENGTH: Final = 80
5758

5859

60+
def validate_iana_time_zone(value: str) -> None:
61+
if not value:
62+
return
63+
try:
64+
ZoneInfo(value)
65+
except ZoneInfoNotFoundError as exc:
66+
raise ValidationError(f"Invalid IANA time zone: {value!r}.") from exc
67+
68+
5969
class TaxonRank(OrderedEnum):
6070
KINGDOM = "KINGDOM"
6171
PHYLUM = "PHYLUM"
@@ -606,6 +616,15 @@ class Deployment(BaseModel):
606616
latitude = models.FloatField(null=True, blank=True)
607617
longitude = models.FloatField(null=True, blank=True)
608618
image = models.ImageField(upload_to="deployments", blank=True, null=True)
619+
time_zone = models.CharField(
620+
max_length=64,
621+
default=settings.TIME_ZONE,
622+
help_text=(
623+
"IANA time zone for this deployment (e.g., 'America/Los_Angeles'). "
624+
"Used as metadata for interpreting local timestamps."
625+
),
626+
validators=[validate_iana_time_zone],
627+
)
609628

610629
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, related_name="deployments")
611630

ami/main/tests.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,25 @@
44
from io import BytesIO
55

66
from django.contrib.auth.models import AnonymousUser
7+
from django.core.exceptions import ValidationError
78
from django.core.files.uploadedfile import SimpleUploadedFile
89
from django.db import connection, models
910
from django.test import TestCase, override_settings
1011
from guardian.shortcuts import assign_perm, get_perms, remove_perm
1112
from PIL import Image
1213
from rest_framework import status
14+
from rest_framework import serializers
1315
from rest_framework.test import APIRequestFactory, APITestCase
1416
from rich import print
1517

1618
from ami.exports.models import DataExport
1719
from ami.jobs.models import VALID_JOB_TYPES, Job
20+
from ami.main.api.serializers import (
21+
DeploymentListSerializer,
22+
DeploymentNestedSerializer,
23+
DeploymentNestedSerializerWithLocationAndCounts,
24+
DeploymentSerializer,
25+
)
1826
from ami.main.models import (
1927
Classification,
2028
Deployment,
@@ -46,6 +54,37 @@
4654
logger = logging.getLogger(__name__)
4755

4856

57+
class TestTimeZoneNormalization(TestCase):
58+
def test_deployment_invalid_time_zone_raises(self):
59+
project = Project.objects.create(name="TZ Project", create_defaults=False)
60+
serializer = DeploymentSerializer(
61+
data={"name": "D1", "project_id": project.pk, "time_zone": "Mars/Phobos"},
62+
context={"request": APIRequestFactory().post("/")},
63+
)
64+
self.assertFalse(serializer.is_valid())
65+
self.assertIn("time_zone", serializer.errors)
66+
67+
def test_deployment_serializers_expose_time_zone(self):
68+
project = Project.objects.create(name="TZ Project", create_defaults=False)
69+
deployment = Deployment.objects.create(project=project, name="D1", time_zone="UTC")
70+
71+
for serializer_cls in (
72+
DeploymentListSerializer,
73+
DeploymentNestedSerializer,
74+
DeploymentNestedSerializerWithLocationAndCounts,
75+
DeploymentSerializer,
76+
):
77+
self.assertIn("time_zone", serializer_cls.Meta.fields)
78+
79+
class DeploymentTimeZoneOnlySerializer(serializers.ModelSerializer):
80+
class Meta:
81+
model = Deployment
82+
fields = ("time_zone",)
83+
84+
data = DeploymentTimeZoneOnlySerializer(deployment).data
85+
self.assertEqual(data["time_zone"], "UTC")
86+
87+
4988
class TestProjectSetup(TestCase):
5089
def test_project_creation(self):
5190
project = Project.objects.create(name="New Project with Defaults", create_defaults=True)

0 commit comments

Comments
 (0)