fix(timezone): fix timer clock-skew
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
from django.utils import timezone
|
||||
|
||||
from core.serializers.base import BaseModelSerializer
|
||||
from apps.time_entries.models import TimeEntry
|
||||
@@ -31,6 +32,27 @@ class TimeEntrySerializer(BaseModelSerializer):
|
||||
tag_details = serializers.SerializerMethodField()
|
||||
start_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
|
||||
end_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", allow_null=True)
|
||||
start_time_ms = serializers.SerializerMethodField()
|
||||
end_time_ms = serializers.SerializerMethodField()
|
||||
server_now_ms = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def _epoch_ms(value):
|
||||
if value is None:
|
||||
return None
|
||||
if timezone.is_naive(value):
|
||||
value = timezone.make_aware(value, timezone.get_current_timezone())
|
||||
return int(value.timestamp() * 1000)
|
||||
|
||||
def get_start_time_ms(self, obj):
|
||||
return self._epoch_ms(obj.start_time)
|
||||
|
||||
def get_end_time_ms(self, obj):
|
||||
return self._epoch_ms(obj.end_time)
|
||||
|
||||
def get_server_now_ms(self, obj):
|
||||
server_now = self.context.get("server_now") or timezone.now()
|
||||
return self._epoch_ms(server_now)
|
||||
|
||||
def get_tags(self, obj):
|
||||
return [str(tag.id) for tag in Tag.all_objects.filter(time_entries=obj).order_by("-updated_at", "-created_at")]
|
||||
@@ -76,7 +98,10 @@ class TimeEntrySerializer(BaseModelSerializer):
|
||||
"project_details",
|
||||
"description",
|
||||
"start_time",
|
||||
"start_time_ms",
|
||||
"end_time",
|
||||
"end_time_ms",
|
||||
"server_now_ms",
|
||||
"duration",
|
||||
"tags",
|
||||
"tag_details",
|
||||
|
||||
@@ -38,6 +38,17 @@ class TimeEntryViewSet(ModelViewSet):
|
||||
filterset_class = TimeEntryFilter
|
||||
search_fields = ["description", "project__name", "project__client__name", "tags__name"]
|
||||
|
||||
@staticmethod
|
||||
def _epoch_ms(value):
|
||||
if timezone.is_naive(value):
|
||||
value = timezone.make_aware(value, timezone.get_current_timezone())
|
||||
return int(value.timestamp() * 1000)
|
||||
|
||||
def _serializer_context(self, *, server_now=None):
|
||||
context = self.get_serializer_context()
|
||||
context["server_now"] = server_now or timezone.now()
|
||||
return context
|
||||
|
||||
@staticmethod
|
||||
def _serialize_duration_ms(entry):
|
||||
if entry.duration is not None:
|
||||
@@ -51,8 +62,12 @@ class TimeEntryViewSet(ModelViewSet):
|
||||
days_since_sunday = (local_dt.weekday() + 1) % 7
|
||||
return (local_dt - timedelta(days=days_since_sunday)).date()
|
||||
|
||||
def _build_grouped_entries(self, entries):
|
||||
serialized_entries = TimeEntrySerializer(entries, many=True, context=self.get_serializer_context()).data
|
||||
def _build_grouped_entries(self, entries, *, server_now):
|
||||
serialized_entries = TimeEntrySerializer(
|
||||
entries,
|
||||
many=True,
|
||||
context=self._serializer_context(server_now=server_now),
|
||||
).data
|
||||
serialized_by_id = {item["id"]: item for item in serialized_entries}
|
||||
weeks = []
|
||||
weeks_by_key = {}
|
||||
@@ -114,6 +129,7 @@ class TimeEntryViewSet(ModelViewSet):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
paginator = self.pagination_class()
|
||||
page = paginator.paginate_queryset(queryset, request, view=self)
|
||||
server_now = timezone.now()
|
||||
current_items_count = len(page)
|
||||
has_more = (paginator.offset + current_items_count) < paginator.count
|
||||
|
||||
@@ -125,7 +141,19 @@ class TimeEntryViewSet(ModelViewSet):
|
||||
"offset": paginator.offset,
|
||||
"next_offset": paginator.offset + current_items_count if has_more else None,
|
||||
"has_more": has_more,
|
||||
"groups": self._build_grouped_entries(page),
|
||||
"server_now_ms": self._epoch_ms(server_now),
|
||||
"server_now": server_now.isoformat(),
|
||||
"groups": self._build_grouped_entries(page, server_now=server_now),
|
||||
}
|
||||
)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="debug-time")
|
||||
def debug_time(self, request):
|
||||
server_now = timezone.now()
|
||||
return Response(
|
||||
{
|
||||
"server_now_ms": self._epoch_ms(server_now),
|
||||
"server_now": server_now.isoformat(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -148,7 +176,7 @@ class TimeEntryViewSet(ModelViewSet):
|
||||
**serializer.validated_data
|
||||
)
|
||||
|
||||
output_serializer = TimeEntrySerializer(entry, context=self.get_serializer_context())
|
||||
output_serializer = TimeEntrySerializer(entry, context=self._serializer_context())
|
||||
return Response(output_serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
@@ -168,7 +196,7 @@ class TimeEntryViewSet(ModelViewSet):
|
||||
**serializer.validated_data
|
||||
)
|
||||
|
||||
output_serializer = TimeEntrySerializer(updated_entry, context=self.get_serializer_context())
|
||||
output_serializer = TimeEntrySerializer(updated_entry, context=self._serializer_context())
|
||||
return Response(output_serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
@@ -189,7 +217,7 @@ class TimeEntryViewSet(ModelViewSet):
|
||||
end_time = serializer.validated_data.get("end_time")
|
||||
stopped_entry = stop_time_entry(entry, end_time=end_time)
|
||||
|
||||
output_serializer = TimeEntrySerializer(stopped_entry, context=self.get_serializer_context())
|
||||
output_serializer = TimeEntrySerializer(stopped_entry, context=self._serializer_context())
|
||||
return Response(output_serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user