diff --git a/backend/server/adventures/migrations/0016_alter_adventureimage_image.py b/backend/server/adventures/migrations/0016_alter_adventureimage_image.py new file mode 100644 index 0000000..a226fe1 --- /dev/null +++ b/backend/server/adventures/migrations/0016_alter_adventureimage_image.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.8 on 2025-01-01 21:40 + +import adventures.models +import django_resized.forms +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0015_transportation_destination_latitude_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='adventureimage', + name='image', + field=django_resized.forms.ResizedImageField(crop=None, force_format='WEBP', keep_meta=True, quality=75, scale=None, size=[1920, 1080], upload_to=adventures.models.PathAndRename('images/')), + ), + ] diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 98ae268..68c8ad6 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -1,7 +1,9 @@ from collections.abc import Collection +import os from typing import Iterable import uuid from django.db import models +from django.utils.deconstruct import deconstructible from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField @@ -257,11 +259,26 @@ class ChecklistItem(models.Model): def __str__(self): return self.name +@deconstructible +class PathAndRename: + def __init__(self, path): + self.path = path + + def __call__(self, instance, filename): + ext = filename.split('.')[-1] + # Generate a new UUID for the filename + filename = f"{uuid.uuid4()}.{ext}" + return os.path.join(self.path, filename) + class AdventureImage(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) - image = ResizedImageField(force_format="WEBP", quality=75, upload_to='images/') + image = ResizedImageField( + force_format="WEBP", + quality=75, + upload_to=PathAndRename('images/') # Use the callable class here + ) adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE) def __str__(self): diff --git a/backend/server/worldtravel/management/commands/download-countries.py b/backend/server/worldtravel/management/commands/download-countries.py index c9dff83..06382cb 100644 --- a/backend/server/worldtravel/management/commands/download-countries.py +++ b/backend/server/worldtravel/management/commands/download-countries.py @@ -68,6 +68,8 @@ class Command(BaseCommand): country_name = country['name'] country_subregion = country['subregion'] country_capital = country['capital'] + longitude = round(float(country['longitude']), 6) if country['longitude'] else None + latitude = round(float(country['latitude']), 6) if country['latitude'] else None processed_country_codes.add(country_code) @@ -76,13 +78,17 @@ class Command(BaseCommand): country_obj.name = country_name country_obj.subregion = country_subregion country_obj.capital = country_capital + country_obj.longitude = longitude + country_obj.latitude = latitude countries_to_update.append(country_obj) else: country_obj = Country( name=country_name, country_code=country_code, subregion=country_subregion, - capital=country_capital + capital=country_capital, + longitude=longitude, + latitude=latitude ) countries_to_create.append(country_obj) diff --git a/backend/server/worldtravel/migrations/0011_country_latitude_country_longitude.py b/backend/server/worldtravel/migrations/0011_country_latitude_country_longitude.py new file mode 100644 index 0000000..8916896 --- /dev/null +++ b/backend/server/worldtravel/migrations/0011_country_latitude_country_longitude.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2025-01-02 00:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0010_country_capital'), + ] + + operations = [ + migrations.AddField( + model_name='country', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='country', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + ] diff --git a/backend/server/worldtravel/models.py b/backend/server/worldtravel/models.py index f7f8f99..2acc629 100644 --- a/backend/server/worldtravel/models.py +++ b/backend/server/worldtravel/models.py @@ -15,6 +15,8 @@ class Country(models.Model): country_code = models.CharField(max_length=2, unique=True) #iso2 code subregion = models.CharField(max_length=100, blank=True, null=True) capital = models.CharField(max_length=100, blank=True, null=True) + longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) class Meta: verbose_name = "Country" diff --git a/backend/server/worldtravel/serializers.py b/backend/server/worldtravel/serializers.py index 0f2ed73..70f569b 100644 --- a/backend/server/worldtravel/serializers.py +++ b/backend/server/worldtravel/serializers.py @@ -29,7 +29,7 @@ class CountrySerializer(serializers.ModelSerializer): class Meta: model = Country fields = '__all__' - read_only_fields = ['id', 'name', 'country_code', 'subregion', 'flag_url', 'num_regions', 'num_visits'] + read_only_fields = ['id', 'name', 'country_code', 'subregion', 'flag_url', 'num_regions', 'num_visits', 'longitude', 'latitude', 'capital'] class RegionSerializer(serializers.ModelSerializer): diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index f405b5b..23960a5 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -1057,6 +1057,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
{#each immichImages as image}
+ Image from Immich + import { goto } from '$app/navigation'; import CountryCard from '$lib/components/CountryCard.svelte'; import type { Country } from '$lib/types'; import type { PageData } from './$types'; import { t } from 'svelte-i18n'; + import { MapLibre, Marker } from 'svelte-maplibre'; export let data: PageData; console.log(data); @@ -12,6 +14,7 @@ let filteredCountries: Country[] = []; const allCountries: Country[] = data.props?.countries || []; let worldSubregions: string[] = []; + let showMap: boolean = false; worldSubregions = [...new Set(allCountries.map((country) => country.subregion))]; // remove blank subregions @@ -96,6 +99,16 @@ {/each} + +
+ + {$t('adventures.show_map')} +
@@ -115,6 +128,33 @@ {/if}
+{#if showMap} +
+ + + + {#each filteredCountries as country} + {#if country.latitude && country.longitude} + goto(`/worldtravel/${country.country_code}`)} + > + + {country.name} + + + {/if} + {/each} + +
+{/if} +
{#each filteredCountries as country} diff --git a/frontend/src/routes/worldtravel/[id]/+page.svelte b/frontend/src/routes/worldtravel/[id]/+page.svelte index 15bd69d..53224e1 100644 --- a/frontend/src/routes/worldtravel/[id]/+page.svelte +++ b/frontend/src/routes/worldtravel/[id]/+page.svelte @@ -161,7 +161,6 @@ {/if} {/each} -