feat(itinerary): add validation for global and dated itinerary items

This commit is contained in:
Sean Morley
2026-05-07 22:54:52 -04:00
parent a319fb149a
commit 9ca0a2044a
3 changed files with 85 additions and 3 deletions

View File

@@ -1060,6 +1060,7 @@ class CollectionItineraryDaySerializer(CustomModelSerializer):
return super().update(instance, validated_data)
class CollectionItineraryItemSerializer(CustomModelSerializer):
date = serializers.DateField(required=False, allow_null=True)
item = serializers.SerializerMethodField()
start_datetime = serializers.ReadOnlyField()
end_datetime = serializers.ReadOnlyField()
@@ -1069,6 +1070,33 @@ class CollectionItineraryItemSerializer(CustomModelSerializer):
model = CollectionItineraryItem
fields = ['id', 'collection', 'content_type', 'object_id', 'item', 'date', 'is_global', 'order', 'start_datetime', 'end_datetime', 'created_at', 'object_name']
read_only_fields = ['id', 'created_at', 'start_datetime', 'end_datetime', 'item', 'object_name']
def validate(self, attrs):
data = super().validate(attrs)
is_global = data.get('is_global')
if is_global is None and self.instance is not None:
is_global = self.instance.is_global
if 'date' in data:
date = data.get('date')
elif self.instance is not None:
date = self.instance.date
else:
date = None
if is_global and date is not None:
raise serializers.ValidationError({
'date': 'Global items must not have a date.',
'is_global': 'Provide either a date or set is_global, not both.',
})
if not is_global and date is None and self.instance is None:
raise serializers.ValidationError({
'date': 'Dated items must include a date. To create a trip-wide item, set is_global=true.',
})
return data
def update(self, instance, validated_data):
# Security: Prevent changing collection, content_type, or object_id after creation

View File

@@ -1,3 +1,57 @@
from django.test import TestCase
from rest_framework.test import APITestCase
# Create your tests here.
from adventures.models import Collection, CollectionItineraryItem, Location
from users.models import CustomUser
class ItineraryAPITestCase(APITestCase):
def setUp(self):
self.user = CustomUser.objects.create_user(
username='itinerary-user',
email='itinerary-user@example.com',
password='testpassword123',
)
self.collection = Collection.objects.create(user=self.user, name='Test Trip')
self.location = Location.objects.create(user=self.user, name='Test Location', is_public=True)
self.client.force_authenticate(user=self.user)
def test_create_global_itinerary_item_without_date(self):
response = self.client.post(
'/api/itineraries/',
{
'collection': str(self.collection.id),
'content_type': 'location',
'object_id': str(self.location.id),
'is_global': True,
'order': 0,
},
format='json',
)
self.assertEqual(response.status_code, 201)
self.assertEqual(CollectionItineraryItem.objects.count(), 1)
item = CollectionItineraryItem.objects.get()
self.assertTrue(item.is_global)
self.assertIsNone(item.date)
self.assertEqual(item.collection, self.collection)
payload = response.json()
self.assertTrue(payload['is_global'])
self.assertIsNone(payload['date'])
def test_create_dated_itinerary_item_without_date_is_rejected(self):
response = self.client.post(
'/api/itineraries/',
{
'collection': str(self.collection.id),
'content_type': 'location',
'object_id': str(self.location.id),
'is_global': False,
'order': 0,
},
format='json',
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()['date'][0], 'Dated items must include a date. To create a trip-wide item, set is_global=true.')

View File

@@ -1,4 +1,4 @@
export let appVersion = 'v0.12.0-dev-042426';
export let appVersion = 'v0.12.0-dev-050726';
export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.12.0';
export let appTitle = 'AdventureLog';
export let copyrightYear = '2023-2026';