diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py
index df0f9ac..5771c7c 100644
--- a/backend/server/adventures/serializers.py
+++ b/backend/server/adventures/serializers.py
@@ -203,6 +203,17 @@ class TransportationSerializer(CustomModelSerializer):
]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
+class HotelSerializer(CustomModelSerializer):
+
+ class Meta:
+ model = Hotel
+ fields = [
+ 'id', 'user_id', 'name', 'description', 'rating', 'link', 'check_in', 'check_out',
+ 'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public',
+ 'collection', 'created_at', 'updated_at'
+ ]
+ read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
+
class NoteSerializer(CustomModelSerializer):
class Meta:
@@ -289,10 +300,11 @@ class CollectionSerializer(CustomModelSerializer):
transportations = TransportationSerializer(many=True, read_only=True, source='transportation_set')
notes = NoteSerializer(many=True, read_only=True, source='note_set')
checklists = ChecklistSerializer(many=True, read_only=True, source='checklist_set')
+ hotels = HotelSerializer(many=True, read_only=True, source='hotel_set')
class Meta:
model = Collection
- fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link']
+ fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'hotels']
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
def to_representation(self, instance):
@@ -302,15 +314,4 @@ class CollectionSerializer(CustomModelSerializer):
for user in instance.shared_with.all():
shared_uuids.append(str(user.uuid))
representation['shared_with'] = shared_uuids
- return representation
-
-class HotelSerializer(CustomModelSerializer):
-
- class Meta:
- model = Hotel
- fields = [
- 'id', 'user_id', 'name', 'description', 'rating', 'link', 'check_in', 'check_out',
- 'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public',
- 'collection', 'created_at', 'updated_at'
- ]
- read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
\ No newline at end of file
+ return representation
\ No newline at end of file
diff --git a/backend/server/worldtravel/management/commands/download-countries.py b/backend/server/worldtravel/management/commands/download-countries.py
index 3a1cd11..9930b8e 100644
--- a/backend/server/worldtravel/management/commands/download-countries.py
+++ b/backend/server/worldtravel/management/commands/download-countries.py
@@ -45,8 +45,9 @@ class Command(BaseCommand):
force = options['force']
batch_size = 100
current_version_json = os.path.join(settings.MEDIA_ROOT, 'data_version.json')
- cdn_version_json = requests.get(f'{ADVENTURELOG_CDN_URL}/data/version.json')
- if cdn_version_json.status_code == 200:
+ try:
+ cdn_version_json = requests.get(f'{ADVENTURELOG_CDN_URL}/data/version.json')
+ cdn_version_json.raise_for_status()
cdn_version = cdn_version_json.json().get('version')
if os.path.exists(current_version_json):
with open(current_version_json, 'r') as f:
@@ -62,8 +63,8 @@ class Command(BaseCommand):
else:
self.stdout.write(self.style.SUCCESS('Data is already up-to-date. Run with --force to re-download'))
return
- else:
- self.stdout.write(self.style.ERROR('Error downloading version.json'))
+ except requests.RequestException as e:
+ self.stdout.write(self.style.ERROR(f'Error fetching version from the CDN: {e}, skipping data import. Try restarting the container once CDN connection has been restored.'))
return
self.stdout.write(self.style.SUCCESS('Fetching latest data from the AdventureLog CDN located at: ' + ADVENTURELOG_CDN_URL))
@@ -90,27 +91,6 @@ class Command(BaseCommand):
self.stdout.write(self.style.ERROR('Error downloading countries_states_cities.json'))
return
-
- # if not os.path.exists(version_json) or force:
- # res = requests.get(f'https://raw.githubusercontent.com/dr5hn/countries-states-cities-database/{COUNTRY_REGION_JSON_VERSION}/json/countries%2Bstates%2Bcities.json')
- # if res.status_code == 200:
- # with open(countries_json_path, 'w') as f:
- # f.write(res.text)
- # self.stdout.write(self.style.SUCCESS('countries+regions+states.json downloaded successfully'))
- # else:
- # self.stdout.write(self.style.ERROR('Error downloading countries+regions+states.json'))
- # return
- # elif not os.path.isfile(countries_json_path):
- # self.stdout.write(self.style.ERROR('countries+regions+states.json is not a file'))
- # return
- # elif os.path.getsize(countries_json_path) == 0:
- # self.stdout.write(self.style.ERROR('countries+regions+states.json is empty'))
- # elif Country.objects.count() == 0 or Region.objects.count() == 0 or City.objects.count() == 0:
- # self.stdout.write(self.style.WARNING('Some region data is missing. Re-importing all data.'))
- # else:
- # self.stdout.write(self.style.SUCCESS('Latest country, region, and state data already downloaded.'))
- # return
-
with open(countries_json_path, 'r') as f:
f = open(countries_json_path, 'rb')
parser = ijson.items(f, 'item')
diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte
index 28cd752..3a5afc9 100644
--- a/frontend/src/lib/components/AdventureModal.svelte
+++ b/frontend/src/lib/components/AdventureModal.svelte
@@ -1,51 +1,35 @@
@@ -810,150 +597,7 @@
-
-
-
- {$t('adventures.location_information')}
-
-
-
-
-
-
-
- {#if is_custom_location}
-
- {/if}
-
-
-
-
-
-
- {#if places.length > 0}
-
-
{$t('adventures.search_results')}
-
-
- {#each places as place}
-
- {/each}
-
-
- {:else if noPlaces}
-
{$t('adventures.no_results')}
- {/if}
-
-
-
-
-
-
- {#each markers as marker}
-
- {/each}
-
- {#if reverseGeocodePlace}
-
-
- {reverseGeocodePlace.city
- ? reverseGeocodePlace.city + ', '
- : ''}{reverseGeocodePlace.region},
- {reverseGeocodePlace.country}
-
-
- {reverseGeocodePlace.region}:
- {reverseGeocodePlace.region_visited
- ? $t('adventures.visited')
- : $t('adventures.not_visited')}
-
- {#if reverseGeocodePlace.city}
-
- {reverseGeocodePlace.city}:
- {reverseGeocodePlace.city_visited
- ? $t('adventures.visited')
- : $t('adventures.not_visited')}
-
- {/if}
-
- {#if !reverseGeocodePlace.region_visited || (!reverseGeocodePlace.city_visited && !willBeMarkedVisited)}
-
- {/if}
- {#if (willBeMarkedVisited && !reverseGeocodePlace.region_visited && reverseGeocodePlace.region_id) || (!reverseGeocodePlace.city_visited && willBeMarkedVisited && reverseGeocodePlace.city_id)}
-
-
-
{reverseGeocodePlace.city
- ? reverseGeocodePlace.city + ', '
- : ''}{reverseGeocodePlace.region},
- {reverseGeocodePlace.country}
- {$t('adventures.will_be_marked')}
-
- {/if}
- {/if}
-
-
-
+
diff --git a/frontend/src/lib/components/HotelModal.svelte b/frontend/src/lib/components/HotelModal.svelte
index e44d1f8..241be48 100644
--- a/frontend/src/lib/components/HotelModal.svelte
+++ b/frontend/src/lib/components/HotelModal.svelte
@@ -6,6 +6,7 @@
import { appVersion } from '$lib/config';
import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
import type { Collection, Hotel, ReverseGeocode, OpenStreetMapPlace, Point } from '$lib/types';
+ import LocationDropdown from './LocationDropdown.svelte';
const dispatch = createEventDispatcher();
@@ -17,7 +18,7 @@
let hotel: Hotel = { ...initializeHotel(hotelToEdit) };
let fullStartDate: string = '';
let fullEndDate: string = '';
- let reverseGeocodePlace: ReverseGeocode | null = null;
+ let reverseGeocodePlace: any | null = null;
let query: string = '';
let places: OpenStreetMapPlace[] = [];
let noPlaces: boolean = false;
@@ -83,34 +84,6 @@
if (event.key === 'Escape') close();
}
- // Geocode location search
- async function geocode(e: Event | null) {
- if (e) e.preventDefault();
- if (!query) {
- alert($t('adventures.no_location'));
- return;
- }
- const res = await fetch(`https://nominatim.openstreetmap.org/search?q=${query}&format=jsonv2`, {
- headers: { 'User-Agent': `AdventureLog / ${appVersion}` }
- });
- const data = (await res.json()) as OpenStreetMapPlace[];
- places = data;
- noPlaces = data.length === 0;
- }
-
- // Set custom location flag based on hotel location
- $: is_custom_location = hotel.location !== (reverseGeocodePlace?.display_name || '');
-
- // Add marker to map
- async function addMarker(e: CustomEvent
) {
- markers = [{ lngLat: e.detail.lngLat, name: '', location: '', activity_type: '' }];
- }
-
- // Clear all markers from the map
- function clearMap() {
- markers = [];
- }
-
// Handle form submission (save hotel)
async function handleSubmit(event: Event) {
event.preventDefault();
@@ -325,106 +298,7 @@
-
-
-
- {$t('adventures.location_information')}
-
-
-
-
-
-
-
- {#if is_custom_location}
-
- {/if}
-
-
-
-
-
-
- {#if places.length > 0}
-
-
{$t('adventures.search_results')}
-
-
- {#each places as place}
-
- {/each}
-
-
- {:else if noPlaces}
-
{$t('adventures.no_results')}
- {/if}
-
-
-
-
-
-
- {#each markers as marker}
-
- {/each}
-
-
-
-
-
-
-
-
- {$t('adventures.location_information')}
-
-
-
-
+
diff --git a/frontend/src/lib/components/LocationDropdown.svelte b/frontend/src/lib/components/LocationDropdown.svelte
new file mode 100644
index 0000000..42328f4
--- /dev/null
+++ b/frontend/src/lib/components/LocationDropdown.svelte
@@ -0,0 +1,336 @@
+
+
+
+
+
+ {$t('adventures.location_information')}
+
+
+
+
+
+
+
+ {#if is_custom_location}
+
+ {/if}
+
+
+
+
+
+
+ {#if places.length > 0}
+
+
{$t('adventures.search_results')}
+
+
+ {#each places as place}
+
+ {/each}
+
+
+ {:else if noPlaces}
+
{$t('adventures.no_results')}
+ {/if}
+
+
+
+
+
+
+ {#each markers as marker}
+
+ {/each}
+
+ {#if reverseGeocodePlace}
+
+
+ {reverseGeocodePlace.city
+ ? reverseGeocodePlace.city + ', '
+ : ''}{reverseGeocodePlace.region},
+ {reverseGeocodePlace.country}
+
+
+ {reverseGeocodePlace.region}:
+ {reverseGeocodePlace.region_visited
+ ? $t('adventures.visited')
+ : $t('adventures.not_visited')}
+
+ {#if reverseGeocodePlace.city}
+
+ {reverseGeocodePlace.city}:
+ {reverseGeocodePlace.city_visited
+ ? $t('adventures.visited')
+ : $t('adventures.not_visited')}
+
+ {/if}
+
+ {#if !reverseGeocodePlace.region_visited || (!reverseGeocodePlace.city_visited && !willBeMarkedVisited)}
+
+ {/if}
+ {#if (willBeMarkedVisited && !reverseGeocodePlace.region_visited && reverseGeocodePlace.region_id) || (!reverseGeocodePlace.city_visited && willBeMarkedVisited && reverseGeocodePlace.city_id)}
+
+
+
{reverseGeocodePlace.city
+ ? reverseGeocodePlace.city + ', '
+ : ''}{reverseGeocodePlace.region},
+ {reverseGeocodePlace.country}
+ {$t('adventures.will_be_marked')}
+
+ {/if}
+ {/if}
+
+
+
diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts
index e339ee0..30f5b27 100644
--- a/frontend/src/lib/types.ts
+++ b/frontend/src/lib/types.ts
@@ -113,6 +113,7 @@ export type Collection = {
end_date: string | null;
transportations?: Transportation[];
notes?: Note[];
+ hotels?: Hotel[];
checklists?: Checklist[];
is_archived?: boolean;
shared_with: string[] | undefined;
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index 4c4e645..011dfbf 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -191,6 +191,7 @@
"no_description_found": "No description found",
"adventure_created": "Adventure created",
"adventure_create_error": "Failed to create adventure",
+ "hotel": "Hotel",
"create_adventure": "Create Adventure",
"adventure_updated": "Adventure updated",
"adventure_update_error": "Failed to update adventure",
diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte
index 6f49525..f19f7c7 100644
--- a/frontend/src/routes/collections/[id]/+page.svelte
+++ b/frontend/src/routes/collections/[id]/+page.svelte
@@ -1,5 +1,5 @@
{#if isShowingLinkModal}
@@ -376,6 +404,15 @@
/>
{/if}
+{#if isShowingHotelModal}
+
(isShowingHotelModal = false)}
+ on:save={saveOrCreateHotel}
+ {collection}
+ />
+{/if}
+
{#if isAdventureModalOpen}
{$t('adventures.checklist')}
+