Files
AdventureLog/backend/server/adventures/utils/itinerary.py
Sean Morley b3e4799b74 feat(itinerary): add itinerary management features and link modal
- Introduced ItineraryViewSet for managing itinerary items with create and reorder functionalities.
- Added itinerary linking capabilities in CollectionModal and CollectionItineraryPlanner components.
- Implemented new ItineraryLinkModal for linking existing items to specific dates.
- Enhanced the frontend with new modals for creating locations, lodging, transportation, notes, and checklists.
- Updated the backend to handle itinerary item creation and reordering with appropriate permissions.
- Improved data handling for unscheduled items and their association with the itinerary.
- Added new dependencies to the frontend for enhanced functionality.
2025-12-17 13:39:41 -05:00

88 lines
2.8 KiB
Python

from typing import List
from django.db import transaction
from rest_framework.exceptions import ValidationError, PermissionDenied
from adventures.models import CollectionItineraryItem
@transaction.atomic
def reorder_itinerary_items(user, items_data: List[dict]):
"""Reorder itinerary items in bulk.
Args:
user: requesting user (for permission checks)
items_data: list of dicts with keys `id`, `date`, `order`
Returns:
List[CollectionItineraryItem]: updated items (unsaved instances are saved by this function)
Raises:
ValidationError, PermissionDenied
"""
if not items_data:
raise ValidationError({"items": "This field is required and must not be empty."})
if not isinstance(items_data, list):
raise ValidationError({"items": "Must be a list of item updates."})
# Resolve ids and fetch items
item_ids = [item.get('id') for item in items_data if item.get('id')]
items_qs = CollectionItineraryItem.objects.filter(id__in=item_ids).select_related('collection')
if items_qs.count() != len(item_ids):
raise ValidationError({"items": "One or more items not found."})
items_map = {str(it.id): it for it in items_qs}
# Permission checks: user must be collection owner or in shared_with
for item_id in item_ids:
item = items_map.get(item_id)
if not item:
continue
collection = item.collection
if not (collection.user == user or collection.shared_with.filter(id=user.id).exists()):
raise PermissionDenied("You do not have permission to modify items in this collection.")
# Two-phase update to avoid unique constraint races:
# 1) assign very large temporary order values (guaranteed > existing orders)
# 2) assign final date/order values
temp_offset = 1_000_000
temp_updates = []
for i, item_data in enumerate(items_data):
item_id = item_data.get('id')
if not item_id:
continue
item = items_map.get(item_id)
if not item:
continue
item.order = temp_offset + i
temp_updates.append(item)
if temp_updates:
CollectionItineraryItem.objects.bulk_update(temp_updates, ['order'])
# Finalize
updated_items = []
for item_data in items_data:
item_id = item_data.get('id')
if not item_id:
continue
item = items_map.get(item_id)
if not item:
continue
new_date = item_data.get('date')
new_order = item_data.get('order')
if new_date is not None:
item.date = new_date
if new_order is not None:
item.order = new_order
updated_items.append(item)
if updated_items:
CollectionItineraryItem.objects.bulk_update(updated_items, ['date', 'order'])
return updated_items