diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index ab8bfc6..226074d 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -21,8 +21,8 @@ class AdventureAdmin(admin.ModelAdmin): class CountryAdmin(admin.ModelAdmin): - list_display = ('name', 'country_code', 'continent', 'number_of_regions') - list_filter = ('continent', 'country_code') + list_display = ('name', 'country_code', 'number_of_regions') + list_filter = ('country_code', 'subregion') def number_of_regions(self, obj): return Region.objects.filter(country=obj).count() diff --git a/backend/server/worldtravel/management/commands/download-countries.py b/backend/server/worldtravel/management/commands/download-countries.py new file mode 100644 index 0000000..411f55b --- /dev/null +++ b/backend/server/worldtravel/management/commands/download-countries.py @@ -0,0 +1,115 @@ +import os +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +import requests +from worldtravel.models import Country, Region +from django.db import transaction +from django.contrib.gis.geos import GEOSGeometry, Polygon, MultiPolygon +from django.contrib.gis.geos.error import GEOSException +import json + +from django.conf import settings + +media_root = settings.MEDIA_ROOT + + +def saveCountryFlag(country_code): + flags_dir = os.path.join(media_root, 'flags') + + # Check if the flags directory exists, if not, create it + if not os.path.exists(flags_dir): + os.makedirs(flags_dir) + + # Check if the flag already exists in the media folder + flag_path = os.path.join(flags_dir, f'{country_code}.png') + if os.path.exists(flag_path): + print(f'Flag for {country_code} already exists') + return + + res = requests.get(f'https://flagcdn.com/h240/{country_code.lower()}.png'.lower()) + if res.status_code == 200: + with open(flag_path, 'wb') as f: + f.write(res.content) + print(f'Flag for {country_code} downloaded') + else: + print(f'Error downloading flag for {country_code}') + +class Command(BaseCommand): + help = 'Imports the world travel data' + + def handle(self, *args, **options): + countries_json_path = os.path.join(settings.MEDIA_ROOT, 'countries+regions.json') + if not os.path.exists(countries_json_path): + res = requests.get('https://raw.githubusercontent.com/dr5hn/countries-states-cities-database/master/countries%2Bstates.json') + if res.status_code == 200: + with open(countries_json_path, 'w') as f: + f.write(res.text) + else: + self.stdout.write(self.style.ERROR('Error downloading countries+regions.json')) + return + + with open(countries_json_path, 'r') as f: + data = json.load(f) + + countries_to_create = [] + regions_to_create = [] + + for country in data: + country_code = country['iso2'] + country_name = country['name'] + country_subregion = country['subregion'] + + country_obj = Country( + name=country_name, + country_code=country_code, + subregion=country_subregion + ) + countries_to_create.append(country_obj) + + saveCountryFlag(country_code) + self.stdout.write(self.style.SUCCESS(f'Country {country_name} prepared')) + + # Bulk create countries first + with transaction.atomic(): + Country.objects.bulk_create(countries_to_create, ignore_conflicts=True) + + # Fetch all countries to get their database IDs + countries = {country.country_code: country for country in Country.objects.all()} + + for country in data: + country_code = country['iso2'] + country_obj = countries[country_code] + + if country['states']: + for state in country['states']: + name = state['name'] + state_id = f"{country_code}-{state['state_code']}" + latitude = round(float(state['latitude']), 6) if state['latitude'] else None + longitude = round(float(state['longitude']), 6) if state['longitude'] else None + + region_obj = Region( + id=state_id, + name=name, + country=country_obj, + longitude=longitude, + latitude=latitude + ) + regions_to_create.append(region_obj) + self.stdout.write(self.style.SUCCESS(f'State {state_id} prepared')) + else: + # Create one region with the name of the country if there are no states + region_obj = Region( + id=f"{country_code}-00", + name=country['name'], + country=country_obj + ) + regions_to_create.append(region_obj) + self.stdout.write(self.style.SUCCESS(f'Region {country_code}-00 prepared for {country["name"]}')) + + # Bulk create regions + with transaction.atomic(): + Region.objects.bulk_create(regions_to_create, ignore_conflicts=True) + + self.stdout.write(self.style.SUCCESS('All data imported successfully')) + + diff --git a/backend/server/worldtravel/migrations/0006_remove_country_continent_country_subregion.py b/backend/server/worldtravel/migrations/0006_remove_country_continent_country_subregion.py new file mode 100644 index 0000000..cefae81 --- /dev/null +++ b/backend/server/worldtravel/migrations/0006_remove_country_continent_country_subregion.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0005_remove_country_geometry_region_geometry'), + ] + + operations = [ + migrations.RemoveField( + model_name='country', + name='continent', + ), + migrations.AddField( + model_name='country', + name='subregion', + field=models.CharField(blank=True, max_length=100, null=True), + ), + ] diff --git a/backend/server/worldtravel/migrations/0007_remove_region_geometry_remove_region_name_en.py b/backend/server/worldtravel/migrations/0007_remove_region_geometry_remove_region_name_en.py new file mode 100644 index 0000000..742221d --- /dev/null +++ b/backend/server/worldtravel/migrations/0007_remove_region_geometry_remove_region_name_en.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0006_remove_country_continent_country_subregion'), + ] + + operations = [ + migrations.RemoveField( + model_name='region', + name='geometry', + ), + migrations.RemoveField( + model_name='region', + name='name_en', + ), + ] diff --git a/backend/server/worldtravel/migrations/0008_region_latitude_region_longitude.py b/backend/server/worldtravel/migrations/0008_region_latitude_region_longitude.py new file mode 100644 index 0000000..bf6d06d --- /dev/null +++ b/backend/server/worldtravel/migrations/0008_region_latitude_region_longitude.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0007_remove_region_geometry_remove_region_name_en'), + ] + + operations = [ + migrations.AddField( + model_name='region', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='region', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + ] diff --git a/backend/server/worldtravel/migrations/0009_alter_country_country_code.py b/backend/server/worldtravel/migrations/0009_alter_country_country_code.py new file mode 100644 index 0000000..b09300d --- /dev/null +++ b/backend/server/worldtravel/migrations/0009_alter_country_country_code.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0008_region_latitude_region_longitude'), + ] + + operations = [ + migrations.AlterField( + model_name='country', + name='country_code', + field=models.CharField(max_length=2, unique=True), + ), + ] diff --git a/backend/server/worldtravel/models.py b/backend/server/worldtravel/models.py index de0d87f..d54fe95 100644 --- a/backend/server/worldtravel/models.py +++ b/backend/server/worldtravel/models.py @@ -9,33 +9,11 @@ User = get_user_model() default_user_id = 1 # Replace with an actual user ID class Country(models.Model): - AFRICA = 'AF' - ANTARCTICA = 'AN' - ASIA = 'AS' - EUROPE = 'EU' - NORTH_AMERICA = 'NA' - OCEANIA = 'OC' - SOUTH_AMERICA = 'SA' - - CONTINENT_CHOICES = [ - (AFRICA, 'Africa'), - (ANTARCTICA, 'Antarctica'), - (ASIA, 'Asia'), - (EUROPE, 'Europe'), - (NORTH_AMERICA, 'North America'), - (OCEANIA, 'Oceania'), - (SOUTH_AMERICA, 'South America'), - ] id = models.AutoField(primary_key=True) name = models.CharField(max_length=100) - country_code = models.CharField(max_length=2) - continent = models.CharField( - max_length=2, - choices=CONTINENT_CHOICES, - default=AFRICA - ) - + country_code = models.CharField(max_length=2, unique=True) #iso2 code + subregion = models.CharField(max_length=100, blank=True, null=True) class Meta: verbose_name = "Country" @@ -47,9 +25,9 @@ class Country(models.Model): class Region(models.Model): id = models.CharField(primary_key=True) name = models.CharField(max_length=100) - name_en = models.CharField(max_length=100, blank=True, null=True) country = models.ForeignKey(Country, on_delete=models.CASCADE) - geometry = gis_models.MultiPolygonField(srid=4326, null=True, blank=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) def __str__(self): return self.name