mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-05-09 23:45:58 -04:00
- 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.
88 lines
2.8 KiB
Python
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
|