From 47efae6f915254660b4ea14b435aba698f24064d Mon Sep 17 00:00:00 2001 From: Giiid Date: Tue, 22 Jul 2025 02:15:32 +0100 Subject: [PATCH 1/2] Display transportation entries in selected time zone not UTC --- backend/server/adventures/serializers.py | 11 ++++++++++- backend/server/adventures/utils/timezone_utils.py | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 backend/server/adventures/utils/timezone_utils.py diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index 622e2eb..6dfbddf 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -1,6 +1,7 @@ from django.utils import timezone import os from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging +from .utils.timezone_utils import format_datetime_in_selected_timezone from rest_framework import serializers from main.utils import CustomModelSerializer from users.serializers import CustomUserDetailsSerializer @@ -254,6 +255,8 @@ class AdventureSerializer(CustomModelSerializer): class TransportationSerializer(CustomModelSerializer): distance = serializers.SerializerMethodField() + start_date_local = serializers.SerializerMethodField() + end_date_local = serializers.SerializerMethodField() class Meta: model = Transportation @@ -262,7 +265,7 @@ class TransportationSerializer(CustomModelSerializer): 'link', 'date', 'flight_number', 'from_location', 'to_location', 'is_public', 'collection', 'created_at', 'updated_at', 'end_date', 'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude', - 'start_timezone', 'end_timezone', 'distance' # ✅ Add distance here + 'start_timezone', 'end_timezone', 'distance', 'start_date_local', 'end_date_local' # ✅ Add distance here ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'distance'] @@ -278,6 +281,12 @@ class TransportationSerializer(CustomModelSerializer): except ValueError: return None return None + + def get_start_date_local(self, obj): + return format_datetime_in_selected_timezone(obj.date, obj.start_timezone) + + def get_end_date_local(self, obj): + return format_datetime_in_selected_timezone(obj.end_date, obj.end_timezone) class LodgingSerializer(CustomModelSerializer): diff --git a/backend/server/adventures/utils/timezone_utils.py b/backend/server/adventures/utils/timezone_utils.py new file mode 100644 index 0000000..fb0625f --- /dev/null +++ b/backend/server/adventures/utils/timezone_utils.py @@ -0,0 +1,8 @@ +import pytz + + +def format_datetime_in_selected_timezone(dt, user_tz): + if dt is None and user_tz is None: + return None + tz = pytz.timezone(user_tz) + return dt.astimezone(tz).isoformat() \ No newline at end of file From d7d75e8a1e9e42f3b042654517497c176f5c4667 Mon Sep 17 00:00:00 2001 From: Giiid Date: Wed, 23 Jul 2025 23:14:35 +0100 Subject: [PATCH 2/2] Add date range validation for visit's adventure within a collection --- backend/server/adventures/admin.py | 5 +++++ backend/server/adventures/models.py | 26 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index e23fa15..4f46b50 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -114,6 +114,11 @@ class VisitAdmin(admin.ModelAdmin): search_fields = ('notes',) + def save_model(self, request, obj, form, change): + obj.full_clean() + super().save_model(request, obj, form, change) + + def image_display(self, obj): if obj.image: # Ensure this field matches your model's image field public_url = os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/') diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 40bb680..cbfebc1 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -548,8 +548,32 @@ class Visit(models.Model): updated_at = models.DateTimeField(auto_now=True) def clean(self): - if self.start_date > self.end_date: + super().clean() + + # Validate start date is before end date + if self.start_date and self.end_date and self.start_date > self.end_date: raise ValidationError('The start date must be before or equal to the end date.') + + # Validates that visit dates fall within their collection date range + if self.start_date and self.end_date and self.adventure: + collections = self.adventure.collections.filter(start_date__isnull=False, end_date__isnull=False) + + if collections.exists(): + visit_start_date = self.start_date.date() if hasattr(self.start_date, 'date') else self.start_date + visit_end_date = self.end_date.date() if hasattr(self.end_date, 'date') else self.end_date + + for collection in collections: + collection_start_date = collection.start_date + collection_end_date = collection.end_date + + if not ( + collection_start_date <= visit_start_date <= collection_end_date and + collection_start_date <= visit_end_date <= collection_end_date + ): + raise ValidationError( + f'Visit dates ({visit_start_date}) to ({visit_end_date}) for {self.adventure.name}' \ + 'should be between the collection date range' + ) def __str__(self): return f"{self.adventure.name} - {self.start_date} to {self.end_date}"