|
3 | 3 | from apps.webcam.models import Webcam |
4 | 4 | from apps.webcam.serializers import WebcamSerializer |
5 | 5 | from rest_framework import viewsets |
6 | | -from datetime import datetime, time, date |
| 6 | +from datetime import datetime, time, date, timedelta |
7 | 7 | from urllib.parse import urlparse |
8 | 8 | from rest_framework.decorators import action |
9 | 9 | from rest_framework.response import Response |
@@ -129,9 +129,46 @@ def _original_image_impl(self, pk): |
129 | 129 |
|
130 | 130 | return FileResponse(open(image_path, 'rb'), content_type='image/jpeg') |
131 | 131 |
|
132 | | - def _timelapse_impl(self, pk): |
| 132 | + def _timelapse_impl(self, request, pk): |
| 133 | + from_date_str = request.query_params.get("from") |
| 134 | + to_date_str = request.query_params.get("to") |
| 135 | + from_time_str = request.query_params.get("time_from") |
| 136 | + to_time_str = request.query_params.get("time_to") |
| 137 | + timezone_str = request.query_params.get("timezone", "UTC") |
| 138 | + |
133 | 139 | timestamps = get_image_list(pk, "TIMELAPSE_HOURS") |
134 | | - return Response(timestamps, status=status.HTTP_200_OK) |
| 140 | + |
| 141 | + # If no filter params provided, return full list unfiltered |
| 142 | + if not any([from_date_str, to_date_str, from_time_str, to_time_str, timezone_str]): |
| 143 | + return Response(timestamps, status=status.HTTP_200_OK) |
| 144 | + |
| 145 | + tz = ZoneInfo(timezone_str) |
| 146 | + |
| 147 | + from_date = parse_date(from_date_str) if from_date_str else None |
| 148 | + to_date = parse_date(to_date_str) if to_date_str else None |
| 149 | + from_time = datetime.strptime(from_time_str, "%H:%M").time() if from_time_str else time.min |
| 150 | + to_time = datetime.strptime(to_time_str, "%H:%M").time() if to_time_str else time.max |
| 151 | + |
| 152 | + # Default: last 24 hours in the requested timezone |
| 153 | + now_local = datetime.now(tz) |
| 154 | + default_from_dt = now_local - timedelta(hours=24) |
| 155 | + |
| 156 | + base_from_date = from_date or default_from_dt.date() |
| 157 | + base_to_date = to_date or now_local.date() |
| 158 | + |
| 159 | + local_from_dt = datetime.combine(base_from_date, from_time, tzinfo=tz) |
| 160 | + local_to_dt = datetime.combine(base_to_date, to_time, tzinfo=tz) |
| 161 | + |
| 162 | + from_dt_utc = local_from_dt.astimezone(ZoneInfo("UTC")) |
| 163 | + to_dt_utc = local_to_dt.astimezone(ZoneInfo("UTC")) |
| 164 | + |
| 165 | + filtered_images = [] |
| 166 | + for timestamp in timestamps: |
| 167 | + img_dt = datetime.strptime(timestamp, "%Y%m%d%H%M%S").replace(tzinfo=ZoneInfo("UTC")) |
| 168 | + if from_dt_utc <= img_dt <= to_dt_utc: |
| 169 | + filtered_images.append(timestamp) |
| 170 | + |
| 171 | + return Response(filtered_images, status=status.HTTP_200_OK) |
135 | 172 |
|
136 | 173 | def _timelapse_image_impl(self, request, pk=None, filename=None): |
137 | 174 | # Environment variables |
@@ -304,7 +341,7 @@ def timelapse_admin(self, request, pk=None): |
304 | 341 | user = request.user |
305 | 342 | if not user.is_authenticated or not user.is_staff: |
306 | 343 | return Response({"detail": "Admin access required."}, status=status.HTTP_403_FORBIDDEN) |
307 | | - return self._timelapse_impl(pk) |
| 344 | + return self._timelapse_impl(request, pk) |
308 | 345 |
|
309 | 346 | @action( |
310 | 347 | detail=True, |
@@ -348,7 +385,7 @@ def original_image_public(self, request, pk=None): |
348 | 385 | ) |
349 | 386 | def timelapse_public(self, request, pk=None): |
350 | 387 | # forward to the real admin logic |
351 | | - return self._timelapse_impl(pk) |
| 388 | + return self._timelapse_impl(request, pk) |
352 | 389 |
|
353 | 390 | @action( |
354 | 391 | detail=True, |
|
0 commit comments