mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-23 06:49:37 +02:00
feat: Add flag URL to Country type and update CountryCard component
This commit is contained in:
parent
77c11fefea
commit
d9e554ad42
14 changed files with 96 additions and 56 deletions
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.7 on 2024-08-05 17:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adventures', '0018_note_links'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -71,6 +71,8 @@ class Collection(models.Model):
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
start_date = models.DateField(blank=True, null=True)
|
start_date = models.DateField(blank=True, null=True)
|
||||||
end_date = models.DateField(blank=True, null=True)
|
end_date = models.DateField(blank=True, null=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
||||||
# if connected adventures are private and collection is public, raise an error
|
# if connected adventures are private and collection is public, raise an error
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
|
@ -98,4 +98,5 @@ class CollectionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Collection
|
model = Collection
|
||||||
# fields are all plus the adventures field
|
# fields are all plus the adventures field
|
||||||
fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes']
|
fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at']
|
||||||
|
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||||
|
|
|
@ -217,9 +217,9 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
order_by = self.request.query_params.get('order_by', 'name')
|
order_by = self.request.query_params.get('order_by', 'name')
|
||||||
order_direction = self.request.query_params.get('order_direction', 'asc')
|
order_direction = self.request.query_params.get('order_direction', 'asc')
|
||||||
|
|
||||||
valid_order_by = ['name']
|
valid_order_by = ['name', 'upated_at']
|
||||||
if order_by not in valid_order_by:
|
if order_by not in valid_order_by:
|
||||||
order_by = 'name'
|
order_by = 'updated_at'
|
||||||
|
|
||||||
if order_direction not in ['asc', 'desc']:
|
if order_direction not in ['asc', 'desc']:
|
||||||
order_direction = 'asc'
|
order_direction = 'asc'
|
||||||
|
@ -228,13 +228,15 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
if order_by == 'name':
|
if order_by == 'name':
|
||||||
queryset = queryset.annotate(lower_name=Lower('name'))
|
queryset = queryset.annotate(lower_name=Lower('name'))
|
||||||
ordering = 'lower_name'
|
ordering = 'lower_name'
|
||||||
|
if order_direction == 'desc':
|
||||||
|
ordering = f'-{ordering}'
|
||||||
else:
|
else:
|
||||||
ordering = order_by
|
order_by == 'updated_at'
|
||||||
|
ordering = 'updated_at'
|
||||||
|
if order_direction == 'asc':
|
||||||
|
ordering = '-updated_at'
|
||||||
|
|
||||||
if order_direction == 'desc':
|
#print(f"Ordering by: {ordering}") # For debugging
|
||||||
ordering = f'-{ordering}'
|
|
||||||
|
|
||||||
print(f"Ordering by: {ordering}") # For debugging
|
|
||||||
|
|
||||||
return queryset.order_by(ordering)
|
return queryset.order_by(ordering)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,36 @@
|
||||||
# myapp/management/commands/seed.py
|
# myapp/management/commands/seed.py
|
||||||
|
|
||||||
|
import os
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
import requests
|
||||||
from worldtravel.models import Country, Region
|
from worldtravel.models import Country, Region
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
|
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}.png')
|
||||||
|
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):
|
class Command(BaseCommand):
|
||||||
help = 'Imports the world travel data'
|
help = 'Imports the world travel data'
|
||||||
|
@ -528,8 +554,10 @@ class Command(BaseCommand):
|
||||||
defaults={'name': name, 'continent': continent}
|
defaults={'name': name, 'continent': continent}
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
|
saveCountryFlag(country_code)
|
||||||
self.stdout.write(f'Inserted {name} into worldtravel countries')
|
self.stdout.write(f'Inserted {name} into worldtravel countries')
|
||||||
else:
|
else:
|
||||||
|
saveCountryFlag(country_code)
|
||||||
self.stdout.write(f'Updated {name} in worldtravel countries')
|
self.stdout.write(f'Updated {name} in worldtravel countries')
|
||||||
|
|
||||||
def sync_regions(self, regions):
|
def sync_regions(self, regions):
|
||||||
|
|
|
@ -1,13 +1,24 @@
|
||||||
|
import os
|
||||||
from .models import Country, Region, VisitedRegion
|
from .models import Country, Region, VisitedRegion
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
class CountrySerializer(serializers.ModelSerializer):
|
class CountrySerializer(serializers.ModelSerializer):
|
||||||
|
def get_public_url(self, obj):
|
||||||
|
return os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/')
|
||||||
|
|
||||||
|
flag_url = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_flag_url(self, obj):
|
||||||
|
public_url = self.get_public_url(obj)
|
||||||
|
return public_url + '/media/' + 'flags/' + obj.country_code + '.png'
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Country
|
model = Country
|
||||||
fields = '__all__' # Serialize all fields of the Adventure model
|
fields = '__all__' # Serialize all fields of the Adventure model
|
||||||
|
|
||||||
class RegionSerializer(serializers.ModelSerializer):
|
class RegionSerializer(serializers.ModelSerializer):
|
||||||
|
flag_url = ''
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = '__all__' # Serialize all fields of the Adventure model
|
fields = '__all__' # Serialize all fields of the Adventure model
|
||||||
|
|
|
@ -16,7 +16,7 @@ from django.contrib.staticfiles import finders
|
||||||
def regions_by_country(request, country_code):
|
def regions_by_country(request, country_code):
|
||||||
# require authentication
|
# require authentication
|
||||||
country = get_object_or_404(Country, country_code=country_code)
|
country = get_object_or_404(Country, country_code=country_code)
|
||||||
regions = Region.objects.filter(country=country)
|
regions = Region.objects.filter(country=country).order_by('name')
|
||||||
serializer = RegionSerializer(regions, many=True)
|
serializer = RegionSerializer(regions, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ services:
|
||||||
- PUBLIC_SERVER_URL=http://server:8000
|
- PUBLIC_SERVER_URL=http://server:8000
|
||||||
- ORIGIN=http://localhost:8080
|
- ORIGIN=http://localhost:8080
|
||||||
- BODY_SIZE_LIMIT=Infinity
|
- BODY_SIZE_LIMIT=Infinity
|
||||||
- ENABLE_ANALYTICS=false
|
|
||||||
ports:
|
ports:
|
||||||
- "8080:3000"
|
- "8080:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { getFlag } from '$lib';
|
import { getFlag } from '$lib';
|
||||||
|
import type { Country } from '$lib/types';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let countryName: string | undefined = undefined;
|
export let country: Country;
|
||||||
export let countryCode: string;
|
|
||||||
|
|
||||||
async function nav() {
|
async function nav() {
|
||||||
goto(`/worldtravel/${countryCode}`);
|
goto(`/worldtravel/${country.country_code}`);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -17,15 +17,10 @@
|
||||||
>
|
>
|
||||||
<figure>
|
<figure>
|
||||||
<!-- svelte-ignore a11y-img-redundant-alt -->
|
<!-- svelte-ignore a11y-img-redundant-alt -->
|
||||||
<img
|
<img src={country.flag_url} alt="No image available" class="w-full h-48 object-cover" />
|
||||||
src={getFlag(240, countryCode) ||
|
|
||||||
'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
|
|
||||||
alt="No image available"
|
|
||||||
class="w-full h-48 object-cover"
|
|
||||||
/>
|
|
||||||
</figure>
|
</figure>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title overflow-ellipsis">{countryName}</h2>
|
<h2 class="card-title overflow-ellipsis">{country.name}</h2>
|
||||||
<div class="card-actions justify-end">
|
<div class="card-actions justify-end">
|
||||||
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
|
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
|
||||||
<button class="btn btn-primary" on:click={nav}>Open</button>
|
<button class="btn btn-primary" on:click={nav}>Open</button>
|
||||||
|
|
|
@ -34,6 +34,7 @@ export type Country = {
|
||||||
name: string;
|
name: string;
|
||||||
country_code: string;
|
country_code: string;
|
||||||
continent: string;
|
continent: string;
|
||||||
|
flag_url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Region = {
|
export type Region = {
|
||||||
|
|
|
@ -249,7 +249,10 @@
|
||||||
{#if data.props.collection}
|
{#if data.props.collection}
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-muted-foreground">Collection</p>
|
<p class="text-sm text-muted-foreground">Collection</p>
|
||||||
<p class="text-base font-medium">{data.props.collection.name}</p>
|
<a
|
||||||
|
class="text-base font-medium link"
|
||||||
|
href="/collections/{data.props.collection.id}">{data.props.collection.name}</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const load = (async (event) => {
|
||||||
let previous = null;
|
let previous = null;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
let adventures: Adventure[] = [];
|
let adventures: Adventure[] = [];
|
||||||
let initialFetch = await fetch(`${serverEndpoint}/api/collections/`, {
|
let initialFetch = await fetch(`${serverEndpoint}/api/collections/?order_by=updated_at`, {
|
||||||
headers: {
|
headers: {
|
||||||
Cookie: `${event.cookies.get('auth')}`
|
Cookie: `${event.cookies.get('auth')}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,6 @@
|
||||||
|
|
||||||
let resultsPerPage: number = 25;
|
let resultsPerPage: number = 25;
|
||||||
|
|
||||||
let currentView: string = 'cards';
|
|
||||||
|
|
||||||
let next: string | null = data.props.next || null;
|
let next: string | null = data.props.next || null;
|
||||||
let previous: string | null = data.props.previous || null;
|
let previous: string | null = data.props.previous || null;
|
||||||
let count = data.props.count || 0;
|
let count = data.props.count || 0;
|
||||||
|
@ -176,18 +174,18 @@
|
||||||
>
|
>
|
||||||
{sidebarOpen ? 'Close Filters' : 'Open Filters'}
|
{sidebarOpen ? 'Close Filters' : 'Open Filters'}
|
||||||
</button>
|
</button>
|
||||||
{#if currentView == 'cards'}
|
|
||||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||||
{#each collections as collection}
|
{#each collections as collection}
|
||||||
<CollectionCard
|
<CollectionCard
|
||||||
type=""
|
type=""
|
||||||
{collection}
|
{collection}
|
||||||
on:delete={deleteCollection}
|
on:delete={deleteCollection}
|
||||||
on:edit={editCollection}
|
on:edit={editCollection}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
<div class="join flex items-center justify-center mt-4">
|
<div class="join flex items-center justify-center mt-4">
|
||||||
{#if next || previous}
|
{#if next || previous}
|
||||||
<div class="join">
|
<div class="join">
|
||||||
|
@ -246,27 +244,9 @@
|
||||||
value="name"
|
value="name"
|
||||||
hidden
|
hidden
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="btn btn-primary mt-4">Filter</button>
|
<button type="submit" class="btn btn-success btn-primary mt-4">Filter</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<h3 class="text-center font-semibold text-lg mb-4">View</h3>
|
|
||||||
<div class="join">
|
|
||||||
<input
|
|
||||||
class="join-item btn-neutral btn"
|
|
||||||
type="radio"
|
|
||||||
name="options"
|
|
||||||
aria-label="Cards"
|
|
||||||
on:click={() => (currentView = 'cards')}
|
|
||||||
checked
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
class="join-item btn btn-neutral"
|
|
||||||
type="radio"
|
|
||||||
name="options"
|
|
||||||
aria-label="Table"
|
|
||||||
on:click={() => (currentView = 'table')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
|
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
|
||||||
{#each countries as country}
|
{#each countries as country}
|
||||||
<CountryCard countryCode={country.country_code} countryName={country.name} />
|
<CountryCard {country} />
|
||||||
<!-- <p>Name: {item.name}, Continent: {item.continent}</p> -->
|
<!-- <p>Name: {item.name}, Continent: {item.continent}</p> -->
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue