API: datetime objects always in UTC

i.e. avoid the pain of time-conversions when 'talking with computers'.
This commit is contained in:
Klaas van Schelven
2025-09-26 15:01:45 +02:00
parent 0ca3e33e1f
commit afd31d2263
6 changed files with 31 additions and 13 deletions

View File

@@ -0,0 +1,13 @@
import datetime
from rest_framework import serializers
class UTCModelSerializer(serializers.ModelSerializer):
def build_standard_field(self, field_name, model_field):
field_class, field_kwargs = super().build_standard_field(field_name, model_field)
if field_class is serializers.DateTimeField:
field_kwargs.setdefault("default_timezone", datetime.timezone.utc)
return field_class, field_kwargs

View File

@@ -1,11 +1,12 @@
from rest_framework import serializers from rest_framework import serializers
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from bugsink.api_serializers import UTCModelSerializer
from .markdown_stacktrace import render_stacktrace_md from .markdown_stacktrace import render_stacktrace_md
from .models import Event from .models import Event
class EventListSerializer(serializers.ModelSerializer): class EventListSerializer(UTCModelSerializer):
"""Lightweight list view: excludes the (potentially large) `data` field.""" """Lightweight list view: excludes the (potentially large) `data` field."""
class Meta: class Meta:
@@ -23,7 +24,7 @@ class EventListSerializer(serializers.ModelSerializer):
] ]
class EventDetailSerializer(serializers.ModelSerializer): class EventDetailSerializer(UTCModelSerializer):
"""Detail view: includes full `data` payload.""" """Detail view: includes full `data` payload."""
# NOTE as with Issue.grouping_keys: check viewset for prefetching # NOTE as with Issue.grouping_keys: check viewset for prefetching
# grouping_key = serializers.CharField(source="grouping.grouping_key", read_only=True) # grouping_key = serializers.CharField(source="grouping.grouping_key", read_only=True)

View File

@@ -1,9 +1,9 @@
from rest_framework import serializers from bugsink.api_serializers import UTCModelSerializer
from .models import Issue from .models import Issue
class IssueSerializer(serializers.ModelSerializer): class IssueSerializer(UTCModelSerializer):
# grouping_keys = serializers.SerializerMethodField() # read-only list of strings # grouping_keys = serializers.SerializerMethodField() # read-only list of strings
class Meta: class Meta:

View File

@@ -1,4 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from bugsink.api_serializers import UTCModelSerializer
from bugsink.api_fields import make_enum_field from bugsink.api_fields import make_enum_field
from teams.models import Team from teams.models import Team
@@ -11,7 +12,7 @@ from .models import Project, ProjectVisibility
ProjectVisibilityField = make_enum_field(ProjectVisibility) ProjectVisibilityField = make_enum_field(ProjectVisibility)
class ProjectListSerializer(serializers.ModelSerializer): class ProjectListSerializer(UTCModelSerializer):
visibility = ProjectVisibilityField() visibility = ProjectVisibilityField()
dsn = serializers.CharField(read_only=True) dsn = serializers.CharField(read_only=True)
@@ -33,7 +34,7 @@ class ProjectListSerializer(serializers.ModelSerializer):
] ]
class ProjectDetailSerializer(ExpandableSerializerMixin, serializers.ModelSerializer): class ProjectDetailSerializer(ExpandableSerializerMixin, UTCModelSerializer):
expandable_fields = {"team": TeamDetailSerializer} expandable_fields = {"team": TeamDetailSerializer}
visibility = ProjectVisibilityField() visibility = ProjectVisibilityField()
dsn = serializers.CharField(read_only=True) dsn = serializers.CharField(read_only=True)
@@ -56,7 +57,7 @@ class ProjectDetailSerializer(ExpandableSerializerMixin, serializers.ModelSerial
] ]
class ProjectCreateUpdateSerializer(serializers.ModelSerializer): class ProjectCreateUpdateSerializer(UTCModelSerializer):
id = serializers.UUIDField(read_only=True) id = serializers.UUIDField(read_only=True)
team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all()) team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all())
visibility = ProjectVisibilityField(required=False) visibility = ProjectVisibilityField(required=False)

View File

@@ -1,5 +1,7 @@
import datetime
from django.utils import timezone from django.utils import timezone
from rest_framework import serializers from rest_framework import serializers
from bugsink.api_serializers import UTCModelSerializer
from projects.models import Project from projects.models import Project
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
@@ -7,13 +9,13 @@ from rest_framework.exceptions import ValidationError
from .models import Release, create_release_if_needed from .models import Release, create_release_if_needed
class ReleaseListSerializer(serializers.ModelSerializer): class ReleaseListSerializer(UTCModelSerializer):
class Meta: class Meta:
model = Release model = Release
fields = ["id", "project", "version", "date_released"] fields = ["id", "project", "version", "date_released"]
class ReleaseDetailSerializer(serializers.ModelSerializer): class ReleaseDetailSerializer(UTCModelSerializer):
class Meta: class Meta:
model = Release model = Release
fields = ["id", "project", "version", "date_released", "semver", "is_semver", "sort_epoch"] fields = ["id", "project", "version", "date_released", "semver", "is_semver", "sort_epoch"]
@@ -23,7 +25,7 @@ class ReleaseDetailSerializer(serializers.ModelSerializer):
class ReleaseCreateSerializer(serializers.Serializer): class ReleaseCreateSerializer(serializers.Serializer):
project = serializers.PrimaryKeyRelatedField(queryset=Project.objects.all()) project = serializers.PrimaryKeyRelatedField(queryset=Project.objects.all())
version = serializers.CharField(allow_blank=True) version = serializers.CharField(allow_blank=True)
timestamp = serializers.DateTimeField(required=False) timestamp = serializers.DateTimeField(required=False, default_timezone=datetime.timezone.utc)
def create(self, validated_data): def create(self, validated_data):
project = validated_data["project"] project = validated_data["project"]

View File

@@ -1,12 +1,13 @@
from rest_framework import serializers from rest_framework import serializers
from bugsink.api_fields import make_enum_field from bugsink.api_fields import make_enum_field
from bugsink.api_serializers import UTCModelSerializer
from .models import Team, TeamVisibility from .models import Team, TeamVisibility
TeamVisibilityField = make_enum_field(TeamVisibility) TeamVisibilityField = make_enum_field(TeamVisibility)
class TeamListSerializer(serializers.ModelSerializer): class TeamListSerializer(UTCModelSerializer):
visibility = TeamVisibilityField() visibility = TeamVisibilityField()
class Meta: class Meta:
@@ -14,7 +15,7 @@ class TeamListSerializer(serializers.ModelSerializer):
fields = ["id", "name", "visibility"] fields = ["id", "name", "visibility"]
class TeamDetailSerializer(serializers.ModelSerializer): class TeamDetailSerializer(UTCModelSerializer):
visibility = TeamVisibilityField() visibility = TeamVisibilityField()
class Meta: class Meta:
@@ -22,7 +23,7 @@ class TeamDetailSerializer(serializers.ModelSerializer):
fields = ["id", "name", "visibility"] fields = ["id", "name", "visibility"]
class TeamCreateUpdateSerializer(serializers.ModelSerializer): class TeamCreateUpdateSerializer(UTCModelSerializer):
id = serializers.UUIDField(read_only=True) id = serializers.UUIDField(read_only=True)
visibility = TeamVisibilityField(required=False) visibility = TeamVisibilityField(required=False)