1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-05 13:15:18 +02:00

Refactor user_id to user in adventures and related models, views, and components

- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
This commit is contained in:
Sean Morley 2025-06-19 16:50:11 -04:00
parent 7a17e0e1d8
commit 241d27a1a6
50 changed files with 918 additions and 792 deletions

View file

@ -1,7 +1,7 @@
import os import os
from django.contrib import admin from django.contrib import admin
from django.utils.html import mark_safe from django.utils.html import mark_safe
from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category, Attachment, Lodging from .models import Location, Checklist, ChecklistItem, Collection, Transportation, Note, LocationImage, Visit, Category, Attachment, Lodging
from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity
from allauth.account.decorators import secure_admin_login from allauth.account.decorators import secure_admin_login
@ -22,8 +22,8 @@ def trigger_geocoding(modeladmin, request, queryset):
class AdventureAdmin(admin.ModelAdmin): class AdventureAdmin(admin.ModelAdmin):
list_display = ('name', 'get_category', 'get_visit_count', 'user_id', 'is_public') list_display = ('name', 'get_category', 'get_visit_count', 'user', 'is_public')
list_filter = ( 'user_id', 'is_public') list_filter = ( 'user', 'is_public')
search_fields = ('name',) search_fields = ('name',)
readonly_fields = ('city', 'region', 'country') readonly_fields = ('city', 'region', 'country')
actions = [trigger_geocoding] actions = [trigger_geocoding]
@ -97,7 +97,7 @@ class CustomUserAdmin(UserAdmin):
return return
class AdventureImageAdmin(admin.ModelAdmin): class AdventureImageAdmin(admin.ModelAdmin):
list_display = ('user_id', 'image_display') list_display = ('user', 'image_display')
def image_display(self, obj): def image_display(self, obj):
if obj.image: if obj.image:
@ -109,7 +109,7 @@ class AdventureImageAdmin(admin.ModelAdmin):
class VisitAdmin(admin.ModelAdmin): class VisitAdmin(admin.ModelAdmin):
list_display = ('adventure', 'start_date', 'end_date', 'notes') list_display = ('location', 'start_date', 'end_date', 'notes')
list_filter = ('start_date', 'end_date') list_filter = ('start_date', 'end_date')
search_fields = ('notes',) search_fields = ('notes',)
@ -125,19 +125,19 @@ class VisitAdmin(admin.ModelAdmin):
image_display.short_description = 'Image Preview' image_display.short_description = 'Image Preview'
class CategoryAdmin(admin.ModelAdmin): class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'user_id', 'display_name', 'icon') list_display = ('name', 'user', 'display_name', 'icon')
search_fields = ('name', 'display_name') search_fields = ('name', 'display_name')
class CollectionAdmin(admin.ModelAdmin): class CollectionAdmin(admin.ModelAdmin):
list_display = ('name', 'user_id', 'is_public') list_display = ('name', 'user', 'is_public')
admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(Adventure, AdventureAdmin) admin.site.register(Location, AdventureAdmin)
admin.site.register(Collection, CollectionAdmin) admin.site.register(Collection, CollectionAdmin)
admin.site.register(Visit, VisitAdmin) admin.site.register(Visit, VisitAdmin)
admin.site.register(Country, CountryAdmin) admin.site.register(Country, CountryAdmin)
@ -147,7 +147,7 @@ admin.site.register(Transportation)
admin.site.register(Note) admin.site.register(Note)
admin.site.register(Checklist) admin.site.register(Checklist)
admin.site.register(ChecklistItem) admin.site.register(ChecklistItem)
admin.site.register(AdventureImage, AdventureImageAdmin) admin.site.register(LocationImage, AdventureImageAdmin)
admin.site.register(Category, CategoryAdmin) admin.site.register(Category, CategoryAdmin)
admin.site.register(City, CityAdmin) admin.site.register(City, CityAdmin)
admin.site.register(VisitedCity) admin.site.register(VisitedCity)

View file

@ -167,7 +167,7 @@ def extractIsoCode(user, data):
return {"error": "No region found"} return {"error": "No region found"}
region = Region.objects.filter(id=iso_code).first() region = Region.objects.filter(id=iso_code).first()
visited_region = VisitedRegion.objects.filter(region=region, user_id=user).first() visited_region = VisitedRegion.objects.filter(region=region, user=user).first()
region_visited = False region_visited = False
city_visited = False city_visited = False
@ -177,7 +177,7 @@ def extractIsoCode(user, data):
if town_city_or_county: if town_city_or_county:
display_name = f"{town_city_or_county}, {region.name}, {country_code}" display_name = f"{town_city_or_county}, {region.name}, {country_code}"
city = City.objects.filter(name__contains=town_city_or_county, region=region).first() city = City.objects.filter(name__contains=town_city_or_county, region=region).first()
visited_city = VisitedCity.objects.filter(city=city, user_id=user).first() visited_city = VisitedCity.objects.filter(city=city, user=user).first()
if visited_region: if visited_region:
region_visited = True region_visited = True

View file

@ -2,7 +2,7 @@
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
from adventures.models import Adventure from adventures.models import Location
class Command(BaseCommand): class Command(BaseCommand):
@ -38,8 +38,8 @@ class Command(BaseCommand):
] ]
for name, location, type_ in adventures: for name, location, type_ in adventures:
Adventure.objects.create( Location.objects.create(
user_id=user, user=user,
name=name, name=name,
location=location, location=location,
type=type_, type=type_,

View file

@ -1,12 +1,12 @@
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
class AdventureManager(models.Manager): class LocationManager(models.Manager):
def retrieve_adventures(self, user, include_owned=False, include_shared=False, include_public=False): def retrieve_adventures(self, user, include_owned=False, include_shared=False, include_public=False):
query = Q() query = Q()
if include_owned: if include_owned:
query |= Q(user_id=user) query |= Q(user=user)
if include_shared: if include_shared:
query |= Q(collections__shared_with=user) query |= Q(collections__shared_with=user)

View file

@ -0,0 +1,91 @@
# Generated by Django 5.2.1 on 2025-06-19 20:29
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
replaces = [('adventures', '0036_rename_adventure_location'), ('adventures', '0037_rename_adventure_visit_location'), ('adventures', '0038_rename_adventureimage_locationimage'), ('adventures', '0039_rename_adventure_locationimage_location'), ('adventures', '0040_rename_adventure_attachment_location'), ('adventures', '0041_rename_user_id_location_user'), ('adventures', '0042_rename_user_id_locationimage_user'), ('adventures', '0043_rename_user_id_attachment_user'), ('adventures', '0044_rename_user_id_collection_user'), ('adventures', '0045_rename_user_id_transportation_user'), ('adventures', '0046_rename_user_id_note_user'), ('adventures', '0047_rename_user_id_checklist_user'), ('adventures', '0048_rename_user_id_checklistitem_user'), ('adventures', '0049_rename_user_id_category_user'), ('adventures', '0050_rename_user_id_lodging_user')]
dependencies = [
('adventures', '0035_remove_adventure_collection_adventure_collections'),
('worldtravel', '0016_remove_city_insert_id_remove_country_insert_id_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RenameModel(
old_name='Adventure',
new_name='Location',
),
migrations.RenameField(
model_name='visit',
old_name='adventure',
new_name='location',
),
migrations.RenameModel(
old_name='AdventureImage',
new_name='LocationImage',
),
migrations.RenameField(
model_name='locationimage',
old_name='adventure',
new_name='location',
),
migrations.RenameField(
model_name='attachment',
old_name='adventure',
new_name='location',
),
migrations.RenameField(
model_name='location',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='locationimage',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='attachment',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='collection',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='transportation',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='note',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='checklist',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='checklistitem',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='category',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='lodging',
old_name='user_id',
new_name='user',
),
]

View file

@ -1,10 +1,9 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
import os import os
from typing import Iterable
import uuid import uuid
from django.db import models from django.db import models
from django.utils.deconstruct import deconstructible from django.utils.deconstruct import deconstructible
from adventures.managers import AdventureManager from adventures.managers import LocationManager
import threading import threading
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
@ -13,46 +12,47 @@ from django_resized import ResizedImageField
from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import timezone from django.utils import timezone
from adventures.utils.timezones import TIMEZONES
def background_geocode_and_assign(adventure_id: str): def background_geocode_and_assign(location_id: str):
print(f"[Adventure Geocode Thread] Starting geocode for adventure {adventure_id}") print(f"[Location Geocode Thread] Starting geocode for location {location_id}")
try: try:
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
if not (adventure.latitude and adventure.longitude): if not (location.latitude and location.longitude):
return return
from adventures.geocoding import reverse_geocode # or wherever you defined it from adventures.geocoding import reverse_geocode # or wherever you defined it
is_visited = adventure.is_visited_status() is_visited = location.is_visited_status()
result = reverse_geocode(adventure.latitude, adventure.longitude, adventure.user_id) result = reverse_geocode(location.latitude, location.longitude, location.user)
if 'region_id' in result: if 'region_id' in result:
region = Region.objects.filter(id=result['region_id']).first() region = Region.objects.filter(id=result['region_id']).first()
if region: if region:
adventure.region = region location.region = region
if is_visited: if is_visited:
VisitedRegion.objects.get_or_create(user_id=adventure.user_id, region=region) VisitedRegion.objects.get_or_create(user=location.user, region=region)
if 'city_id' in result: if 'city_id' in result:
city = City.objects.filter(id=result['city_id']).first() city = City.objects.filter(id=result['city_id']).first()
if city: if city:
adventure.city = city location.city = city
if is_visited: if is_visited:
VisitedCity.objects.get_or_create(user_id=adventure.user_id, city=city) VisitedCity.objects.get_or_create(user=location.user, city=city)
if 'country_id' in result: if 'country_id' in result:
country = Country.objects.filter(country_code=result['country_id']).first() country = Country.objects.filter(country_code=result['country_id']).first()
if country: if country:
adventure.country = country location.country = country
# Save updated location info # Save updated location info
# Save updated location info, skip geocode threading # Save updated location info, skip geocode threading
adventure.save(update_fields=["region", "city", "country"], _skip_geocode=True) location.save(update_fields=["region", "city", "country"], _skip_geocode=True)
# print(f"[Adventure Geocode Thread] Successfully processed {adventure_id}: {adventure.name} - {adventure.latitude}, {adventure.longitude}") # print(f"[Adventure Geocode Thread] Successfully processed {adventure_id}: {adventure.name} - {adventure.latitude}, {adventure.longitude}")
except Exception as e: except Exception as e:
# Optional: log or print the error # Optional: log or print the error
print(f"[Adventure Geocode Thread] Error processing {adventure_id}: {e}") print(f"[Location Geocode Thread] Error processing {location_id}: {e}")
def validate_file_extension(value): def validate_file_extension(value):
import os import os
@ -62,6 +62,7 @@ def validate_file_extension(value):
if not ext.lower() in valid_extensions: if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.') raise ValidationError('Unsupported file extension.')
# Legacy support for old adventure types, not used in newer versions since custom categories are now used
ADVENTURE_TYPES = [ ADVENTURE_TYPES = [
('general', 'General 🌍'), ('general', 'General 🌍'),
('outdoor', 'Outdoor 🏞️'), ('outdoor', 'Outdoor 🏞️'),
@ -87,426 +88,6 @@ ADVENTURE_TYPES = [
('other', 'Other') ('other', 'Other')
] ]
TIMEZONES = [
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
"Africa/Bissau",
"Africa/Blantyre",
"Africa/Brazzaville",
"Africa/Bujumbura",
"Africa/Cairo",
"Africa/Casablanca",
"Africa/Ceuta",
"Africa/Conakry",
"Africa/Dakar",
"Africa/Dar_es_Salaam",
"Africa/Djibouti",
"Africa/Douala",
"Africa/El_Aaiun",
"Africa/Freetown",
"Africa/Gaborone",
"Africa/Harare",
"Africa/Johannesburg",
"Africa/Juba",
"Africa/Kampala",
"Africa/Khartoum",
"Africa/Kigali",
"Africa/Kinshasa",
"Africa/Lagos",
"Africa/Libreville",
"Africa/Lome",
"Africa/Luanda",
"Africa/Lubumbashi",
"Africa/Lusaka",
"Africa/Malabo",
"Africa/Maputo",
"Africa/Maseru",
"Africa/Mbabane",
"Africa/Mogadishu",
"Africa/Monrovia",
"Africa/Nairobi",
"Africa/Ndjamena",
"Africa/Niamey",
"Africa/Nouakchott",
"Africa/Ouagadougou",
"Africa/Porto-Novo",
"Africa/Sao_Tome",
"Africa/Tripoli",
"Africa/Tunis",
"Africa/Windhoek",
"America/Adak",
"America/Anchorage",
"America/Anguilla",
"America/Antigua",
"America/Araguaina",
"America/Argentina/La_Rioja",
"America/Argentina/Rio_Gallegos",
"America/Argentina/Salta",
"America/Argentina/San_Juan",
"America/Argentina/San_Luis",
"America/Argentina/Tucuman",
"America/Argentina/Ushuaia",
"America/Aruba",
"America/Asuncion",
"America/Bahia",
"America/Bahia_Banderas",
"America/Barbados",
"America/Belem",
"America/Belize",
"America/Blanc-Sablon",
"America/Boa_Vista",
"America/Bogota",
"America/Boise",
"America/Buenos_Aires",
"America/Cambridge_Bay",
"America/Campo_Grande",
"America/Cancun",
"America/Caracas",
"America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Chicago",
"America/Chihuahua",
"America/Ciudad_Juarez",
"America/Coral_Harbour",
"America/Cordoba",
"America/Costa_Rica",
"America/Creston",
"America/Cuiaba",
"America/Curacao",
"America/Danmarkshavn",
"America/Dawson",
"America/Dawson_Creek",
"America/Denver",
"America/Detroit",
"America/Dominica",
"America/Edmonton",
"America/Eirunepe",
"America/El_Salvador",
"America/Fort_Nelson",
"America/Fortaleza",
"America/Glace_Bay",
"America/Godthab",
"America/Goose_Bay",
"America/Grand_Turk",
"America/Grenada",
"America/Guadeloupe",
"America/Guatemala",
"America/Guayaquil",
"America/Guyana",
"America/Halifax",
"America/Havana",
"America/Hermosillo",
"America/Indiana/Knox",
"America/Indiana/Marengo",
"America/Indiana/Petersburg",
"America/Indiana/Tell_City",
"America/Indiana/Vevay",
"America/Indiana/Vincennes",
"America/Indiana/Winamac",
"America/Indianapolis",
"America/Inuvik",
"America/Iqaluit",
"America/Jamaica",
"America/Jujuy",
"America/Juneau",
"America/Kentucky/Monticello",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Los_Angeles",
"America/Louisville",
"America/Lower_Princes",
"America/Maceio",
"America/Managua",
"America/Manaus",
"America/Marigot",
"America/Martinique",
"America/Matamoros",
"America/Mazatlan",
"America/Mendoza",
"America/Menominee",
"America/Merida",
"America/Metlakatla",
"America/Mexico_City",
"America/Miquelon",
"America/Moncton",
"America/Monterrey",
"America/Montevideo",
"America/Montserrat",
"America/Nassau",
"America/New_York",
"America/Nome",
"America/Noronha",
"America/North_Dakota/Beulah",
"America/North_Dakota/Center",
"America/North_Dakota/New_Salem",
"America/Ojinaga",
"America/Panama",
"America/Paramaribo",
"America/Phoenix",
"America/Port-au-Prince",
"America/Port_of_Spain",
"America/Porto_Velho",
"America/Puerto_Rico",
"America/Punta_Arenas",
"America/Rankin_Inlet",
"America/Recife",
"America/Regina",
"America/Resolute",
"America/Rio_Branco",
"America/Santarem",
"America/Santiago",
"America/Santo_Domingo",
"America/Sao_Paulo",
"America/Scoresbysund",
"America/Sitka",
"America/St_Barthelemy",
"America/St_Johns",
"America/St_Kitts",
"America/St_Lucia",
"America/St_Thomas",
"America/St_Vincent",
"America/Swift_Current",
"America/Tegucigalpa",
"America/Thule",
"America/Tijuana",
"America/Toronto",
"America/Tortola",
"America/Vancouver",
"America/Whitehorse",
"America/Winnipeg",
"America/Yakutat",
"Antarctica/Casey",
"Antarctica/Davis",
"Antarctica/DumontDUrville",
"Antarctica/Macquarie",
"Antarctica/Mawson",
"Antarctica/McMurdo",
"Antarctica/Palmer",
"Antarctica/Rothera",
"Antarctica/Syowa",
"Antarctica/Troll",
"Antarctica/Vostok",
"Arctic/Longyearbyen",
"Asia/Aden",
"Asia/Almaty",
"Asia/Amman",
"Asia/Anadyr",
"Asia/Aqtau",
"Asia/Aqtobe",
"Asia/Ashgabat",
"Asia/Atyrau",
"Asia/Baghdad",
"Asia/Bahrain",
"Asia/Baku",
"Asia/Bangkok",
"Asia/Barnaul",
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
"Asia/Calcutta",
"Asia/Chita",
"Asia/Colombo",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
"Asia/Dubai",
"Asia/Dushanbe",
"Asia/Famagusta",
"Asia/Gaza",
"Asia/Hebron",
"Asia/Hong_Kong",
"Asia/Hovd",
"Asia/Irkutsk",
"Asia/Jakarta",
"Asia/Jayapura",
"Asia/Jerusalem",
"Asia/Kabul",
"Asia/Kamchatka",
"Asia/Karachi",
"Asia/Katmandu",
"Asia/Khandyga",
"Asia/Krasnoyarsk",
"Asia/Kuala_Lumpur",
"Asia/Kuching",
"Asia/Kuwait",
"Asia/Macau",
"Asia/Magadan",
"Asia/Makassar",
"Asia/Manila",
"Asia/Muscat",
"Asia/Nicosia",
"Asia/Novokuznetsk",
"Asia/Novosibirsk",
"Asia/Omsk",
"Asia/Oral",
"Asia/Phnom_Penh",
"Asia/Pontianak",
"Asia/Pyongyang",
"Asia/Qatar",
"Asia/Qostanay",
"Asia/Qyzylorda",
"Asia/Rangoon",
"Asia/Riyadh",
"Asia/Saigon",
"Asia/Sakhalin",
"Asia/Samarkand",
"Asia/Seoul",
"Asia/Shanghai",
"Asia/Singapore",
"Asia/Srednekolymsk",
"Asia/Taipei",
"Asia/Tashkent",
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Thimphu",
"Asia/Tokyo",
"Asia/Tomsk",
"Asia/Ulaanbaatar",
"Asia/Urumqi",
"Asia/Ust-Nera",
"Asia/Vientiane",
"Asia/Vladivostok",
"Asia/Yakutsk",
"Asia/Yekaterinburg",
"Asia/Yerevan",
"Atlantic/Azores",
"Atlantic/Bermuda",
"Atlantic/Canary",
"Atlantic/Cape_Verde",
"Atlantic/Faeroe",
"Atlantic/Madeira",
"Atlantic/Reykjavik",
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
"Australia/Lindeman",
"Australia/Lord_Howe",
"Australia/Melbourne",
"Australia/Perth",
"Australia/Sydney",
"Europe/Amsterdam",
"Europe/Andorra",
"Europe/Astrakhan",
"Europe/Athens",
"Europe/Belgrade",
"Europe/Berlin",
"Europe/Bratislava",
"Europe/Brussels",
"Europe/Bucharest",
"Europe/Budapest",
"Europe/Busingen",
"Europe/Chisinau",
"Europe/Copenhagen",
"Europe/Dublin",
"Europe/Gibraltar",
"Europe/Guernsey",
"Europe/Helsinki",
"Europe/Isle_of_Man",
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kaliningrad",
"Europe/Kiev",
"Europe/Kirov",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
"Europe/Luxembourg",
"Europe/Madrid",
"Europe/Malta",
"Europe/Mariehamn",
"Europe/Minsk",
"Europe/Monaco",
"Europe/Moscow",
"Europe/Oslo",
"Europe/Paris",
"Europe/Podgorica",
"Europe/Prague",
"Europe/Riga",
"Europe/Rome",
"Europe/Samara",
"Europe/San_Marino",
"Europe/Sarajevo",
"Europe/Saratov",
"Europe/Simferopol",
"Europe/Skopje",
"Europe/Sofia",
"Europe/Stockholm",
"Europe/Tallinn",
"Europe/Tirane",
"Europe/Ulyanovsk",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
"Europe/Vilnius",
"Europe/Volgograd",
"Europe/Warsaw",
"Europe/Zagreb",
"Europe/Zurich",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
"Indian/Cocos",
"Indian/Comoro",
"Indian/Kerguelen",
"Indian/Mahe",
"Indian/Maldives",
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Bougainville",
"Pacific/Chatham",
"Pacific/Easter",
"Pacific/Efate",
"Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
"Pacific/Galapagos",
"Pacific/Gambier",
"Pacific/Guadalcanal",
"Pacific/Guam",
"Pacific/Honolulu",
"Pacific/Kiritimati",
"Pacific/Kosrae",
"Pacific/Kwajalein",
"Pacific/Majuro",
"Pacific/Marquesas",
"Pacific/Midway",
"Pacific/Nauru",
"Pacific/Niue",
"Pacific/Norfolk",
"Pacific/Noumea",
"Pacific/Pago_Pago",
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Ponape",
"Pacific/Port_Moresby",
"Pacific/Rarotonga",
"Pacific/Saipan",
"Pacific/Tahiti",
"Pacific/Tarawa",
"Pacific/Tongatapu",
"Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis"
]
LODGING_TYPES = [ LODGING_TYPES = [
('hotel', 'Hotel'), ('hotel', 'Hotel'),
('hostel', 'Hostel'), ('hostel', 'Hostel'),
@ -533,13 +114,13 @@ TRANSPORTATION_TYPES = [
] ]
# Assuming you have a default user ID you want to use # Assuming you have a default user ID you want to use
default_user_id = 1 # Replace with an actual user ID default_user = 1 # Replace with an actual user ID
User = get_user_model() User = get_user_model()
class Visit(models.Model): class Visit(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
adventure = models.ForeignKey('Adventure', on_delete=models.CASCADE, related_name='visits') location = models.ForeignKey('Location', on_delete=models.CASCADE, related_name='visits')
start_date = models.DateTimeField(null=True, blank=True) start_date = models.DateTimeField(null=True, blank=True)
end_date = models.DateTimeField(null=True, blank=True) end_date = models.DateTimeField(null=True, blank=True)
timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True) timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True)
@ -552,13 +133,13 @@ class Visit(models.Model):
raise ValidationError('The start date must be before or equal to the end date.') raise ValidationError('The start date must be before or equal to the end date.')
def __str__(self): def __str__(self):
return f"{self.adventure.name} - {self.start_date} to {self.end_date}" return f"{self.location.name} - {self.start_date} to {self.end_date}"
class Adventure(models.Model): class Location(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True) category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
@ -583,7 +164,7 @@ class Adventure(models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
objects = AdventureManager() objects = LocationManager()
def is_visited_status(self): def is_visited_status(self):
current_date = timezone.now().date() current_date = timezone.now().date()
@ -609,18 +190,18 @@ class Adventure(models.Model):
if self.pk: # Only check if the instance has been saved if self.pk: # Only check if the instance has been saved
for collection in self.collections.all(): for collection in self.collections.all():
if collection.is_public and not self.is_public: if collection.is_public and not self.is_public:
raise ValidationError(f'Adventures associated with a public collection must be public. Collection: {collection.name} Adventure: {self.name}') raise ValidationError(f'Locations associated with a public collection must be public. Collection: {collection.name} Location: {self.name}')
# Only enforce same-user constraint for non-shared collections # Only enforce same-user constraint for non-shared collections
if self.user_id != collection.user_id: if self.user != collection.user:
# Check if this is a shared collection scenario # Check if this is a shared collection scenario
# Allow if the adventure owner has access to the collection through sharing # Allow if the location owner has access to the collection through sharing
if not collection.shared_with.filter(uuid=self.user_id.uuid).exists(): if not collection.shared_with.filter(uuid=self.user.uuid).exists():
raise ValidationError(f'Adventures must be associated with collections owned by the same user or shared collections. Collection owner: {collection.user_id.username} Adventure owner: {self.user_id.username}') raise ValidationError(f'Locations must be associated with collections owned by the same user or shared collections. Collection owner: {collection.user.username} Location owner: {self.user.username}')
if self.category: if self.category:
if self.user_id != self.category.user_id: if self.user != self.category.user:
raise ValidationError(f'Adventures must be associated with categories owned by the same user. Category owner: {self.category.user_id.username} Adventure owner: {self.user_id.username}') raise ValidationError(f'Locations must be associated with categories owned by the same user. Category owner: {self.category.user.username} Location owner: {self.user.username}')
def save(self, force_insert=False, force_update=False, using=None, update_fields=None, _skip_geocode=False, _skip_shared_validation=False): def save(self, force_insert=False, force_update=False, using=None, update_fields=None, _skip_geocode=False, _skip_shared_validation=False):
if force_insert and force_update: if force_insert and force_update:
@ -628,7 +209,7 @@ class Adventure(models.Model):
if not self.category: if not self.category:
category, _ = Category.objects.get_or_create( category, _ = Category.objects.get_or_create(
user_id=self.user_id, user=self.user,
name='general', name='general',
defaults={'display_name': 'General', 'icon': '🌍'} defaults={'display_name': 'General', 'icon': '🌍'}
) )
@ -662,8 +243,8 @@ class Adventure(models.Model):
class Collection(models.Model): class Collection(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
is_public = models.BooleanField(default=False) is_public = models.BooleanField(default=False)
@ -681,7 +262,7 @@ class Collection(models.Model):
# Updated to use the new related_name 'adventures' # Updated to use the new related_name 'adventures'
for adventure in self.adventures.all(): for adventure in self.adventures.all():
if not adventure.is_public: if not adventure.is_public:
raise ValidationError(f'Public collections cannot be associated with private adventures. Collection: {self.name} Adventure: {adventure.name}') raise ValidationError(f'Public collections cannot be associated with private locations. Collection: {self.name} Adventure: {adventure.name}')
def __str__(self): def __str__(self):
return self.name return self.name
@ -689,8 +270,8 @@ class Collection(models.Model):
class Transportation(models.Model): class Transportation(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES) type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
@ -720,8 +301,8 @@ class Transportation(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name) raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Transportations must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username) raise ValidationError('Transportations must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Transportation owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -729,8 +310,8 @@ class Transportation(models.Model):
class Note(models.Model): class Note(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
content = models.TextField(blank=True, null=True) content = models.TextField(blank=True, null=True)
links = ArrayField(models.URLField(), blank=True, null=True) links = ArrayField(models.URLField(), blank=True, null=True)
@ -744,8 +325,8 @@ class Note(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Notes associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name) raise ValidationError('Notes associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Notes must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username) raise ValidationError('Notes must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Transportation owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -753,8 +334,8 @@ class Note(models.Model):
class Checklist(models.Model): class Checklist(models.Model):
# id = models.AutoField(primary_key=True) # id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
date = models.DateField(blank=True, null=True) date = models.DateField(blank=True, null=True)
is_public = models.BooleanField(default=False) is_public = models.BooleanField(default=False)
@ -766,8 +347,8 @@ class Checklist(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Checklists associated with a public collection must be public. Collection: ' + self.collection.name + ' Checklist: ' + self.name) raise ValidationError('Checklists associated with a public collection must be public. Collection: ' + self.collection.name + ' Checklist: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Checklists must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Checklist owner: ' + self.user_id.username) raise ValidationError('Checklists must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Checklist owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -775,8 +356,8 @@ class Checklist(models.Model):
class ChecklistItem(models.Model): class ChecklistItem(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
is_checked = models.BooleanField(default=False) is_checked = models.BooleanField(default=False)
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE) checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE)
@ -786,8 +367,8 @@ class ChecklistItem(models.Model):
def clean(self): def clean(self):
if self.checklist.is_public and not self.checklist.is_public: if self.checklist.is_public and not self.checklist.is_public:
raise ValidationError('Checklist items associated with a public checklist must be public. Checklist: ' + self.checklist.name + ' Checklist item: ' + self.name) raise ValidationError('Checklist items associated with a public checklist must be public. Checklist: ' + self.checklist.name + ' Checklist item: ' + self.name)
if self.user_id != self.checklist.user_id: if self.user != self.checklist.user:
raise ValidationError('Checklist items must be associated with checklists owned by the same user. Checklist owner: ' + self.checklist.user_id.username + ' Checklist item owner: ' + self.user_id.username) raise ValidationError('Checklist items must be associated with checklists owned by the same user. Checklist owner: ' + self.checklist.user.username + ' Checklist item owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -803,9 +384,9 @@ class PathAndRename:
filename = f"{uuid.uuid4()}.{ext}" filename = f"{uuid.uuid4()}.{ext}"
return os.path.join(self.path, filename) return os.path.join(self.path, filename)
class AdventureImage(models.Model): class LocationImage(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) 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) user = models.ForeignKey(User, on_delete=models.CASCADE, default=default_user)
image = ResizedImageField( image = ResizedImageField(
force_format="WEBP", force_format="WEBP",
quality=75, quality=75,
@ -814,7 +395,7 @@ class AdventureImage(models.Model):
null=True, null=True,
) )
immich_id = models.CharField(max_length=200, null=True, blank=True) immich_id = models.CharField(max_length=200, null=True, blank=True)
adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE) location = models.ForeignKey(Location, related_name='images', on_delete=models.CASCADE)
is_primary = models.BooleanField(default=False) is_primary = models.BooleanField(default=False)
def clean(self): def clean(self):
@ -843,10 +424,10 @@ class AdventureImage(models.Model):
class Attachment(models.Model): class Attachment(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
file = models.FileField(upload_to=PathAndRename('attachments/'),validators=[validate_file_extension]) file = models.FileField(upload_to=PathAndRename('attachments/'),validators=[validate_file_extension])
adventure = models.ForeignKey(Adventure, related_name='attachments', on_delete=models.CASCADE) location = models.ForeignKey(Location, related_name='attachments', on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True, blank=True) name = models.CharField(max_length=200, null=True, blank=True)
def __str__(self): def __str__(self):
@ -854,15 +435,15 @@ class Attachment(models.Model):
class Category(models.Model): class Category(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
display_name = models.CharField(max_length=200) display_name = models.CharField(max_length=200)
icon = models.CharField(max_length=200, default='🌍') icon = models.CharField(max_length=200, default='🌍')
class Meta: class Meta:
verbose_name_plural = 'Categories' verbose_name_plural = 'Categories'
unique_together = ['name', 'user_id'] unique_together = ['name', 'user']
def clean(self) -> None: def clean(self) -> None:
self.name = self.name.lower().strip() self.name = self.name.lower().strip()
@ -875,8 +456,8 @@ class Category(models.Model):
class Lodging(models.Model): class Lodging(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other') type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other')
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
@ -901,9 +482,9 @@ class Lodging(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Lodging associated with a public collection must be public. Collection: ' + self.collection.name + ' Loging: ' + self.name) raise ValidationError('Lodging associated with a public collection must be public. Collection: ' + self.collection.name + ' Lodging: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Lodging must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Lodging owner: ' + self.user_id.username) raise ValidationError('Lodging must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Lodging owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name

View file

@ -7,8 +7,8 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return True return True
# obj.user_id is FK to User, compare with request.user # obj.user is FK to User, compare with request.user
return obj.user_id == request.user return obj.user == request.user
class IsPublicReadOnly(permissions.BasePermission): class IsPublicReadOnly(permissions.BasePermission):
@ -17,8 +17,8 @@ class IsPublicReadOnly(permissions.BasePermission):
""" """
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return obj.is_public or obj.user_id == request.user return obj.is_public or obj.user == request.user
return obj.user_id == request.user return obj.user == request.user
class CollectionShared(permissions.BasePermission): class CollectionShared(permissions.BasePermission):
@ -48,10 +48,10 @@ class CollectionShared(permissions.BasePermission):
# Read permission if public or owner # Read permission if public or owner
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return obj.is_public or obj.user_id == user return obj.is_public or obj.user == user
# Write permission only if owner or shared user via collections # Write permission only if owner or shared user via collections
if obj.user_id == user: if obj.user == user:
return True return True
if hasattr(obj, 'collections'): if hasattr(obj, 'collections'):
@ -76,7 +76,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
if obj.is_public: if obj.is_public:
return True return True
if obj.user_id == user: if obj.user == user:
return True return True
# If user in shared_with of any collection related to obj # If user in shared_with of any collection related to obj
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists(): if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
@ -88,7 +88,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
return False return False
# For write methods, allow if owner or shared user # For write methods, allow if owner or shared user
if obj.user_id == user: if obj.user == user:
return True return True
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists(): if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
return True return True

View file

@ -1,6 +1,6 @@
from django.utils import timezone from django.utils import timezone
import os import os
from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging from .models import Location, LocationImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging
from rest_framework import serializers from rest_framework import serializers
from main.utils import CustomModelSerializer from main.utils import CustomModelSerializer
from users.serializers import CustomUserDetailsSerializer from users.serializers import CustomUserDetailsSerializer
@ -11,15 +11,15 @@ from integrations.models import ImmichIntegration
class AdventureImageSerializer(CustomModelSerializer): class AdventureImageSerializer(CustomModelSerializer):
class Meta: class Meta:
model = AdventureImage model = LocationImage
fields = ['id', 'image', 'adventure', 'is_primary', 'user_id', 'immich_id'] fields = ['id', 'image', 'location', 'is_primary', 'user', 'immich_id']
read_only_fields = ['id', 'user_id'] read_only_fields = ['id', 'user']
def to_representation(self, instance): def to_representation(self, instance):
# If immich_id is set, check for user integration once # If immich_id is set, check for user integration once
integration = None integration = None
if instance.immich_id: if instance.immich_id:
integration = ImmichIntegration.objects.filter(user=instance.user_id).first() integration = ImmichIntegration.objects.filter(user=instance.user).first()
if not integration: if not integration:
return None # Skip if Immich image but no integration return None # Skip if Immich image but no integration
@ -42,8 +42,8 @@ class AttachmentSerializer(CustomModelSerializer):
extension = serializers.SerializerMethodField() extension = serializers.SerializerMethodField()
class Meta: class Meta:
model = Attachment model = Attachment
fields = ['id', 'file', 'adventure', 'extension', 'name', 'user_id'] fields = ['id', 'file', 'location', 'extension', 'name', 'user']
read_only_fields = ['id', 'user_id'] read_only_fields = ['id', 'user']
def get_extension(self, obj): def get_extension(self, obj):
return obj.file.name.split('.')[-1] return obj.file.name.split('.')[-1]
@ -71,7 +71,7 @@ class CategorySerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
user = self.context['request'].user user = self.context['request'].user
validated_data['name'] = validated_data['name'].lower() validated_data['name'] = validated_data['name'].lower()
return Category.objects.create(user_id=user, **validated_data) return Category.objects.create(user=user, **validated_data)
def update(self, instance, validated_data): def update(self, instance, validated_data):
for attr, value in validated_data.items(): for attr, value in validated_data.items():
@ -82,7 +82,7 @@ class CategorySerializer(serializers.ModelSerializer):
return instance return instance
def get_num_adventures(self, obj): def get_num_adventures(self, obj):
return Adventure.objects.filter(category=obj, user_id=obj.user_id).count() return Location.objects.filter(category=obj, user=obj.user).count()
class VisitSerializer(serializers.ModelSerializer): class VisitSerializer(serializers.ModelSerializer):
@ -108,13 +108,13 @@ class AdventureSerializer(CustomModelSerializer):
) )
class Meta: class Meta:
model = Adventure model = Location
fields = [ fields = [
'id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'id', 'name', 'description', 'rating', 'activity_types', 'location',
'is_public', 'collections', 'created_at', 'updated_at', 'images', 'link', 'longitude', 'is_public', 'collections', 'created_at', 'updated_at', 'images', 'link', 'longitude',
'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user', 'city', 'country', 'region' 'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user', 'city', 'country', 'region'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'is_visited', 'user'] read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'is_visited']
def get_images(self, obj): def get_images(self, obj):
serializer = AdventureImageSerializer(obj.images.all(), many=True, context=self.context) serializer = AdventureImageSerializer(obj.images.all(), many=True, context=self.context)
@ -128,7 +128,7 @@ class AdventureSerializer(CustomModelSerializer):
user = self.context['request'].user user = self.context['request'].user
for collection in collections: for collection in collections:
if collection.user_id != user: if collection.user != user:
raise serializers.ValidationError( raise serializers.ValidationError(
f"Collection '{collection.name}' does not belong to the current user." f"Collection '{collection.name}' does not belong to the current user."
) )
@ -140,7 +140,7 @@ class AdventureSerializer(CustomModelSerializer):
if category_data: if category_data:
user = self.context['request'].user user = self.context['request'].user
name = category_data.get('name', '').lower() name = category_data.get('name', '').lower()
existing_category = Category.objects.filter(user_id=user, name=name).first() existing_category = Category.objects.filter(user=user, name=name).first()
if existing_category: if existing_category:
return existing_category return existing_category
category_data['name'] = name category_data['name'] = name
@ -162,7 +162,7 @@ class AdventureSerializer(CustomModelSerializer):
icon = category_data.icon icon = category_data.icon
category, created = Category.objects.get_or_create( category, created = Category.objects.get_or_create(
user_id=user, user=user,
name=name, name=name,
defaults={ defaults={
'display_name': display_name, 'display_name': display_name,
@ -172,7 +172,7 @@ class AdventureSerializer(CustomModelSerializer):
return category return category
def get_user(self, obj): def get_user(self, obj):
user = obj.user_id user = obj.user
return CustomUserDetailsSerializer(user).data return CustomUserDetailsSerializer(user).data
def get_is_visited(self, obj): def get_is_visited(self, obj):
@ -184,11 +184,11 @@ class AdventureSerializer(CustomModelSerializer):
collections_data = validated_data.pop('collections', []) collections_data = validated_data.pop('collections', [])
print(category_data) print(category_data)
adventure = Adventure.objects.create(**validated_data) adventure = Location.objects.create(**validated_data)
# Handle visits # Handle visits
for visit_data in visits_data: for visit_data in visits_data:
Visit.objects.create(adventure=adventure, **visit_data) Visit.objects.create(location=adventure, **visit_data)
# Handle category # Handle category
if category_data: if category_data:
@ -216,7 +216,7 @@ class AdventureSerializer(CustomModelSerializer):
# Handle category - ONLY allow the adventure owner to change categories # Handle category - ONLY allow the adventure owner to change categories
user = self.context['request'].user user = self.context['request'].user
if category_data and instance.user_id == user: if category_data and instance.user == user:
# Only the owner can set categories # Only the owner can set categories
category = self.get_or_create_category(category_data) category = self.get_or_create_category(category_data)
instance.category = category instance.category = category
@ -241,7 +241,7 @@ class AdventureSerializer(CustomModelSerializer):
visit.save() visit.save()
updated_visit_ids.add(visit_id) updated_visit_ids.add(visit_id)
else: else:
new_visit = Visit.objects.create(adventure=instance, **visit_data) new_visit = Visit.objects.create(location=instance, **visit_data)
updated_visit_ids.add(new_visit.id) updated_visit_ids.add(new_visit.id)
visits_to_delete = current_visit_ids - updated_visit_ids visits_to_delete = current_visit_ids - updated_visit_ids
@ -258,13 +258,13 @@ class TransportationSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Transportation model = Transportation
fields = [ fields = [
'id', 'user_id', 'type', 'name', 'description', 'rating', 'id', 'user', 'type', 'name', 'description', 'rating',
'link', 'date', 'flight_number', 'from_location', 'to_location', 'link', 'date', 'flight_number', 'from_location', 'to_location',
'is_public', 'collection', 'created_at', 'updated_at', 'end_date', 'is_public', 'collection', 'created_at', 'updated_at', 'end_date',
'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude', 'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude',
'start_timezone', 'end_timezone', 'distance' # ✅ Add distance here 'start_timezone', 'end_timezone', 'distance'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'distance'] read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'distance']
def get_distance(self, obj): def get_distance(self, obj):
if ( if (
@ -284,29 +284,29 @@ class LodgingSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Lodging model = Lodging
fields = [ fields = [
'id', 'user_id', 'name', 'description', 'rating', 'link', 'check_in', 'check_out', 'id', 'user', 'name', 'description', 'rating', 'link', 'check_in', 'check_out',
'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public', 'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public',
'collection', 'created_at', 'updated_at', 'type', 'timezone' 'collection', 'created_at', 'updated_at', 'type', 'timezone'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
class NoteSerializer(CustomModelSerializer): class NoteSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Note model = Note
fields = [ fields = [
'id', 'user_id', 'name', 'content', 'date', 'links', 'id', 'user', 'name', 'content', 'date', 'links',
'is_public', 'collection', 'created_at', 'updated_at' 'is_public', 'collection', 'created_at', 'updated_at'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
class ChecklistItemSerializer(CustomModelSerializer): class ChecklistItemSerializer(CustomModelSerializer):
class Meta: class Meta:
model = ChecklistItem model = ChecklistItem
fields = [ fields = [
'id', 'user_id', 'name', 'is_checked', 'checklist', 'created_at', 'updated_at' 'id', 'user', 'name', 'is_checked', 'checklist', 'created_at', 'updated_at'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'checklist'] read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'checklist']
class ChecklistSerializer(CustomModelSerializer): class ChecklistSerializer(CustomModelSerializer):
items = ChecklistItemSerializer(many=True, source='checklistitem_set') items = ChecklistItemSerializer(many=True, source='checklistitem_set')
@ -314,21 +314,21 @@ class ChecklistSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Checklist model = Checklist
fields = [ fields = [
'id', 'user_id', 'name', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'items' 'id', 'user', 'name', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'items'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
def create(self, validated_data): def create(self, validated_data):
items_data = validated_data.pop('checklistitem_set') items_data = validated_data.pop('checklistitem_set')
checklist = Checklist.objects.create(**validated_data) checklist = Checklist.objects.create(**validated_data)
for item_data in items_data: for item_data in items_data:
# Remove user_id from item_data to avoid constraint issues # Remove user from item_data to avoid constraint issues
item_data.pop('user_id', None) item_data.pop('user', None)
# Set user_id from the parent checklist # Set user from the parent checklist
ChecklistItem.objects.create( ChecklistItem.objects.create(
checklist=checklist, checklist=checklist,
user_id=checklist.user_id, user=checklist.user,
**item_data **item_data
) )
return checklist return checklist
@ -348,8 +348,8 @@ class ChecklistSerializer(CustomModelSerializer):
# Update or create items # Update or create items
updated_item_ids = set() updated_item_ids = set()
for item_data in items_data: for item_data in items_data:
# Remove user_id from item_data to avoid constraint issues # Remove user from item_data to avoid constraint issues
item_data.pop('user_id', None) item_data.pop('user', None)
item_id = item_data.get('id') item_id = item_data.get('id')
if item_id: if item_id:
@ -363,14 +363,14 @@ class ChecklistSerializer(CustomModelSerializer):
# If ID is provided but doesn't exist, create new item # If ID is provided but doesn't exist, create new item
ChecklistItem.objects.create( ChecklistItem.objects.create(
checklist=instance, checklist=instance,
user_id=instance.user_id, user=instance.user,
**item_data **item_data
) )
else: else:
# If no ID is provided, create new item # If no ID is provided, create new item
ChecklistItem.objects.create( ChecklistItem.objects.create(
checklist=instance, checklist=instance,
user_id=instance.user_id, user=instance.user,
**item_data **item_data
) )
@ -399,8 +399,8 @@ class CollectionSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Collection 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', 'lodging'] fields = ['id', 'description', 'user', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'lodging']
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
def to_representation(self, instance): def to_representation(self, instance):
representation = super().to_representation(instance) representation = super().to_representation(instance)

View file

@ -1,8 +1,8 @@
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.dispatch import receiver from django.dispatch import receiver
from adventures.models import Adventure from adventures.models import Location
@receiver(m2m_changed, sender=Adventure.collections.through) @receiver(m2m_changed, sender=Location.collections.through)
def update_adventure_publicity(sender, instance, action, **kwargs): def update_adventure_publicity(sender, instance, action, **kwargs):
""" """
Signal handler to update adventure publicity when collections are added/removed Signal handler to update adventure publicity when collections are added/removed

View file

@ -1,4 +1,4 @@
from adventures.models import AdventureImage, Attachment from adventures.models import LocationImage, Attachment
protected_paths = ['images/', 'attachments/'] protected_paths = ['images/', 'attachments/']
@ -10,10 +10,10 @@ def checkFilePermission(fileId, user, mediaType):
# Construct the full relative path to match the database field # Construct the full relative path to match the database field
image_path = f"images/{fileId}" image_path = f"images/{fileId}"
# Fetch the AdventureImage object # Fetch the AdventureImage object
adventure = AdventureImage.objects.get(image=image_path).adventure adventure = LocationImage.objects.get(image=image_path).location
if adventure.is_public: if adventure.is_public:
return True return True
elif adventure.user_id == user: elif adventure.user == user:
return True return True
elif adventure.collections.exists(): elif adventure.collections.exists():
# Check if the user is in any collection's shared_with list # Check if the user is in any collection's shared_with list
@ -23,7 +23,7 @@ def checkFilePermission(fileId, user, mediaType):
return False return False
else: else:
return False return False
except AdventureImage.DoesNotExist: except LocationImage.DoesNotExist:
return False return False
elif mediaType == 'attachments/': elif mediaType == 'attachments/':
try: try:
@ -31,10 +31,10 @@ def checkFilePermission(fileId, user, mediaType):
attachment_path = f"attachments/{fileId}" attachment_path = f"attachments/{fileId}"
# Fetch the Attachment object # Fetch the Attachment object
attachment = Attachment.objects.get(file=attachment_path) attachment = Attachment.objects.get(file=attachment_path)
adventure = attachment.adventure adventure = attachment.location
if adventure.is_public: if adventure.is_public:
return True return True
elif adventure.user_id == user: elif adventure.user == user:
return True return True
elif adventure.collections.exists(): elif adventure.collections.exists():
# Check if the user is in any collection's shared_with list # Check if the user is in any collection's shared_with list

View file

@ -0,0 +1,419 @@
TIMEZONES = [
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
"Africa/Bissau",
"Africa/Blantyre",
"Africa/Brazzaville",
"Africa/Bujumbura",
"Africa/Cairo",
"Africa/Casablanca",
"Africa/Ceuta",
"Africa/Conakry",
"Africa/Dakar",
"Africa/Dar_es_Salaam",
"Africa/Djibouti",
"Africa/Douala",
"Africa/El_Aaiun",
"Africa/Freetown",
"Africa/Gaborone",
"Africa/Harare",
"Africa/Johannesburg",
"Africa/Juba",
"Africa/Kampala",
"Africa/Khartoum",
"Africa/Kigali",
"Africa/Kinshasa",
"Africa/Lagos",
"Africa/Libreville",
"Africa/Lome",
"Africa/Luanda",
"Africa/Lubumbashi",
"Africa/Lusaka",
"Africa/Malabo",
"Africa/Maputo",
"Africa/Maseru",
"Africa/Mbabane",
"Africa/Mogadishu",
"Africa/Monrovia",
"Africa/Nairobi",
"Africa/Ndjamena",
"Africa/Niamey",
"Africa/Nouakchott",
"Africa/Ouagadougou",
"Africa/Porto-Novo",
"Africa/Sao_Tome",
"Africa/Tripoli",
"Africa/Tunis",
"Africa/Windhoek",
"America/Adak",
"America/Anchorage",
"America/Anguilla",
"America/Antigua",
"America/Araguaina",
"America/Argentina/La_Rioja",
"America/Argentina/Rio_Gallegos",
"America/Argentina/Salta",
"America/Argentina/San_Juan",
"America/Argentina/San_Luis",
"America/Argentina/Tucuman",
"America/Argentina/Ushuaia",
"America/Aruba",
"America/Asuncion",
"America/Bahia",
"America/Bahia_Banderas",
"America/Barbados",
"America/Belem",
"America/Belize",
"America/Blanc-Sablon",
"America/Boa_Vista",
"America/Bogota",
"America/Boise",
"America/Buenos_Aires",
"America/Cambridge_Bay",
"America/Campo_Grande",
"America/Cancun",
"America/Caracas",
"America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Chicago",
"America/Chihuahua",
"America/Ciudad_Juarez",
"America/Coral_Harbour",
"America/Cordoba",
"America/Costa_Rica",
"America/Creston",
"America/Cuiaba",
"America/Curacao",
"America/Danmarkshavn",
"America/Dawson",
"America/Dawson_Creek",
"America/Denver",
"America/Detroit",
"America/Dominica",
"America/Edmonton",
"America/Eirunepe",
"America/El_Salvador",
"America/Fort_Nelson",
"America/Fortaleza",
"America/Glace_Bay",
"America/Godthab",
"America/Goose_Bay",
"America/Grand_Turk",
"America/Grenada",
"America/Guadeloupe",
"America/Guatemala",
"America/Guayaquil",
"America/Guyana",
"America/Halifax",
"America/Havana",
"America/Hermosillo",
"America/Indiana/Knox",
"America/Indiana/Marengo",
"America/Indiana/Petersburg",
"America/Indiana/Tell_City",
"America/Indiana/Vevay",
"America/Indiana/Vincennes",
"America/Indiana/Winamac",
"America/Indianapolis",
"America/Inuvik",
"America/Iqaluit",
"America/Jamaica",
"America/Jujuy",
"America/Juneau",
"America/Kentucky/Monticello",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Los_Angeles",
"America/Louisville",
"America/Lower_Princes",
"America/Maceio",
"America/Managua",
"America/Manaus",
"America/Marigot",
"America/Martinique",
"America/Matamoros",
"America/Mazatlan",
"America/Mendoza",
"America/Menominee",
"America/Merida",
"America/Metlakatla",
"America/Mexico_City",
"America/Miquelon",
"America/Moncton",
"America/Monterrey",
"America/Montevideo",
"America/Montserrat",
"America/Nassau",
"America/New_York",
"America/Nome",
"America/Noronha",
"America/North_Dakota/Beulah",
"America/North_Dakota/Center",
"America/North_Dakota/New_Salem",
"America/Ojinaga",
"America/Panama",
"America/Paramaribo",
"America/Phoenix",
"America/Port-au-Prince",
"America/Port_of_Spain",
"America/Porto_Velho",
"America/Puerto_Rico",
"America/Punta_Arenas",
"America/Rankin_Inlet",
"America/Recife",
"America/Regina",
"America/Resolute",
"America/Rio_Branco",
"America/Santarem",
"America/Santiago",
"America/Santo_Domingo",
"America/Sao_Paulo",
"America/Scoresbysund",
"America/Sitka",
"America/St_Barthelemy",
"America/St_Johns",
"America/St_Kitts",
"America/St_Lucia",
"America/St_Thomas",
"America/St_Vincent",
"America/Swift_Current",
"America/Tegucigalpa",
"America/Thule",
"America/Tijuana",
"America/Toronto",
"America/Tortola",
"America/Vancouver",
"America/Whitehorse",
"America/Winnipeg",
"America/Yakutat",
"Antarctica/Casey",
"Antarctica/Davis",
"Antarctica/DumontDUrville",
"Antarctica/Macquarie",
"Antarctica/Mawson",
"Antarctica/McMurdo",
"Antarctica/Palmer",
"Antarctica/Rothera",
"Antarctica/Syowa",
"Antarctica/Troll",
"Antarctica/Vostok",
"Arctic/Longyearbyen",
"Asia/Aden",
"Asia/Almaty",
"Asia/Amman",
"Asia/Anadyr",
"Asia/Aqtau",
"Asia/Aqtobe",
"Asia/Ashgabat",
"Asia/Atyrau",
"Asia/Baghdad",
"Asia/Bahrain",
"Asia/Baku",
"Asia/Bangkok",
"Asia/Barnaul",
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
"Asia/Calcutta",
"Asia/Chita",
"Asia/Colombo",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
"Asia/Dubai",
"Asia/Dushanbe",
"Asia/Famagusta",
"Asia/Gaza",
"Asia/Hebron",
"Asia/Hong_Kong",
"Asia/Hovd",
"Asia/Irkutsk",
"Asia/Jakarta",
"Asia/Jayapura",
"Asia/Jerusalem",
"Asia/Kabul",
"Asia/Kamchatka",
"Asia/Karachi",
"Asia/Katmandu",
"Asia/Khandyga",
"Asia/Krasnoyarsk",
"Asia/Kuala_Lumpur",
"Asia/Kuching",
"Asia/Kuwait",
"Asia/Macau",
"Asia/Magadan",
"Asia/Makassar",
"Asia/Manila",
"Asia/Muscat",
"Asia/Nicosia",
"Asia/Novokuznetsk",
"Asia/Novosibirsk",
"Asia/Omsk",
"Asia/Oral",
"Asia/Phnom_Penh",
"Asia/Pontianak",
"Asia/Pyongyang",
"Asia/Qatar",
"Asia/Qostanay",
"Asia/Qyzylorda",
"Asia/Rangoon",
"Asia/Riyadh",
"Asia/Saigon",
"Asia/Sakhalin",
"Asia/Samarkand",
"Asia/Seoul",
"Asia/Shanghai",
"Asia/Singapore",
"Asia/Srednekolymsk",
"Asia/Taipei",
"Asia/Tashkent",
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Thimphu",
"Asia/Tokyo",
"Asia/Tomsk",
"Asia/Ulaanbaatar",
"Asia/Urumqi",
"Asia/Ust-Nera",
"Asia/Vientiane",
"Asia/Vladivostok",
"Asia/Yakutsk",
"Asia/Yekaterinburg",
"Asia/Yerevan",
"Atlantic/Azores",
"Atlantic/Bermuda",
"Atlantic/Canary",
"Atlantic/Cape_Verde",
"Atlantic/Faeroe",
"Atlantic/Madeira",
"Atlantic/Reykjavik",
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
"Australia/Lindeman",
"Australia/Lord_Howe",
"Australia/Melbourne",
"Australia/Perth",
"Australia/Sydney",
"Europe/Amsterdam",
"Europe/Andorra",
"Europe/Astrakhan",
"Europe/Athens",
"Europe/Belgrade",
"Europe/Berlin",
"Europe/Bratislava",
"Europe/Brussels",
"Europe/Bucharest",
"Europe/Budapest",
"Europe/Busingen",
"Europe/Chisinau",
"Europe/Copenhagen",
"Europe/Dublin",
"Europe/Gibraltar",
"Europe/Guernsey",
"Europe/Helsinki",
"Europe/Isle_of_Man",
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kaliningrad",
"Europe/Kiev",
"Europe/Kirov",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
"Europe/Luxembourg",
"Europe/Madrid",
"Europe/Malta",
"Europe/Mariehamn",
"Europe/Minsk",
"Europe/Monaco",
"Europe/Moscow",
"Europe/Oslo",
"Europe/Paris",
"Europe/Podgorica",
"Europe/Prague",
"Europe/Riga",
"Europe/Rome",
"Europe/Samara",
"Europe/San_Marino",
"Europe/Sarajevo",
"Europe/Saratov",
"Europe/Simferopol",
"Europe/Skopje",
"Europe/Sofia",
"Europe/Stockholm",
"Europe/Tallinn",
"Europe/Tirane",
"Europe/Ulyanovsk",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
"Europe/Vilnius",
"Europe/Volgograd",
"Europe/Warsaw",
"Europe/Zagreb",
"Europe/Zurich",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
"Indian/Cocos",
"Indian/Comoro",
"Indian/Kerguelen",
"Indian/Mahe",
"Indian/Maldives",
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Bougainville",
"Pacific/Chatham",
"Pacific/Easter",
"Pacific/Efate",
"Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
"Pacific/Galapagos",
"Pacific/Gambier",
"Pacific/Guadalcanal",
"Pacific/Guam",
"Pacific/Honolulu",
"Pacific/Kiritimati",
"Pacific/Kosrae",
"Pacific/Kwajalein",
"Pacific/Majuro",
"Pacific/Marquesas",
"Pacific/Midway",
"Pacific/Nauru",
"Pacific/Niue",
"Pacific/Norfolk",
"Pacific/Noumea",
"Pacific/Pago_Pago",
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Ponape",
"Pacific/Port_Moresby",
"Pacific/Rarotonga",
"Pacific/Saipan",
"Pacific/Tahiti",
"Pacific/Tarawa",
"Pacific/Tongatapu",
"Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis"
]

View file

@ -2,7 +2,7 @@ from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Adventure from adventures.models import Location
class ActivityTypesView(viewsets.ViewSet): class ActivityTypesView(viewsets.ViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -18,7 +18,7 @@ class ActivityTypesView(viewsets.ViewSet):
Returns: Returns:
Response: A response containing a list of distinct activity types. Response: A response containing a list of distinct activity types.
""" """
types = Adventure.objects.filter(user_id=request.user.id).values_list('activity_types', flat=True).distinct() types = Location.objects.filter(user=request.user.id).values_list('activity_types', flat=True).distinct()
allTypes = [] allTypes = []

View file

@ -4,7 +4,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from django.db.models import Q from django.db.models import Q
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from adventures.models import Adventure, AdventureImage from adventures.models import Location, LocationImage
from adventures.serializers import AdventureImageSerializer from adventures.serializers import AdventureImageSerializer
from integrations.models import ImmichIntegration from integrations.models import ImmichIntegration
import uuid import uuid
@ -26,7 +26,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
instance = self.get_object() instance = self.get_object()
adventure = instance.adventure adventure = instance.adventure
if adventure.user_id != request.user: if adventure.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
# Check if the image is already the primary image # Check if the image is already the primary image
@ -34,7 +34,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST) return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST)
# Set the current primary image to false # Set the current primary image to false
AdventureImage.objects.filter(adventure=adventure, is_primary=True).update(is_primary=False) LocationImage.objects.filter(adventure=adventure, is_primary=True).update(is_primary=False)
# Set the new image to true # Set the new image to true
instance.is_primary = True instance.is_primary = True
@ -46,11 +46,11 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure') adventure_id = request.data.get('adventure')
try: try:
adventure = Adventure.objects.get(id=adventure_id) adventure = Location.objects.get(id=adventure_id)
except Adventure.DoesNotExist: except Location.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND) return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user: if adventure.user != request.user:
# Check if the adventure has any collections # Check if the adventure has any collections
if adventure.collections.exists(): if adventure.collections.exists():
# Check if the user is in the shared_with list of any of the adventure's collections # Check if the user is in the shared_with list of any of the adventure's collections
@ -66,7 +66,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
# Handle Immich ID for shared users by downloading the image # Handle Immich ID for shared users by downloading the image
if (request.user != adventure.user_id and if (request.user != adventure.user and
'immich_id' in request.data and 'immich_id' in request.data and
request.data.get('immich_id')): request.data.get('immich_id')):
@ -74,7 +74,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
# Get the shared user's Immich integration # Get the shared user's Immich integration
try: try:
user_integration = ImmichIntegration.objects.get(user_id=request.user) user_integration = ImmichIntegration.objects.get(user=request.user)
except ImmichIntegration.DoesNotExist: except ImmichIntegration.DoesNotExist:
return Response({ return Response({
"error": "No Immich integration found for your account. Please set up Immich integration first.", "error": "No Immich integration found for your account. Please set up Immich integration first.",
@ -122,7 +122,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
# Save with the downloaded image # Save with the downloaded image
adventure = serializer.validated_data['adventure'] adventure = serializer.validated_data['adventure']
serializer.save(user_id=adventure.user_id, image=image_file) serializer.save(user=adventure.user, image=image_file)
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
@ -145,11 +145,11 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
adventure_id = request.data.get('adventure') adventure_id = request.data.get('adventure')
try: try:
adventure = Adventure.objects.get(id=adventure_id) adventure = Location.objects.get(id=adventure_id)
except Adventure.DoesNotExist: except Location.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND) return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user: if adventure.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return super().update(request, *args, **kwargs) return super().update(request, *args, **kwargs)
@ -165,7 +165,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
instance = self.get_object() instance = self.get_object()
adventure = instance.adventure adventure = instance.adventure
if adventure.user_id != request.user: if adventure.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)
@ -176,7 +176,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
instance = self.get_object() instance = self.get_object()
adventure = instance.adventure adventure = instance.adventure
if adventure.user_id != request.user: if adventure.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return super().partial_update(request, *args, **kwargs) return super().partial_update(request, *args, **kwargs)
@ -192,9 +192,9 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST) return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST)
# Updated queryset to include images from adventures the user owns OR has shared access to # Updated queryset to include images from adventures the user owns OR has shared access to
queryset = AdventureImage.objects.filter( queryset = LocationImage.objects.filter(
Q(adventure__id=adventure_uuid) & ( Q(adventure__id=adventure_uuid) & (
Q(adventure__user_id=request.user) | # User owns the adventure Q(adventure__user=request.user) | # User owns the adventure
Q(adventure__collections__shared_with=request.user) # User has shared access via collection Q(adventure__collections__shared_with=request.user) # User has shared access via collection
) )
).distinct() ).distinct()
@ -204,12 +204,12 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
# Updated to include images from adventures the user owns OR has shared access to # Updated to include images from adventures the user owns OR has shared access to
return AdventureImage.objects.filter( return LocationImage.objects.filter(
Q(adventure__user_id=self.request.user) | # User owns the adventure Q(adventure__user=self.request.user) | # User owns the adventure
Q(adventure__collections__shared_with=self.request.user) # User has shared access via collection Q(adventure__collections__shared_with=self.request.user) # User has shared access via collection
).distinct() ).distinct()
def perform_create(self, serializer): def perform_create(self, serializer):
# Always set the image owner to the adventure owner, not the current user # Always set the image owner to the adventure owner, not the current user
adventure = serializer.validated_data['adventure'] adventure = serializer.validated_data['adventure']
serializer.save(user_id=adventure.user_id) serializer.save(user=adventure.user)

View file

@ -8,7 +8,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
import requests import requests
from adventures.models import Adventure, Category, Transportation, Lodging from adventures.models import Location, Category, Transportation, Lodging
from adventures.permissions import IsOwnerOrSharedWithFullAccess from adventures.permissions import IsOwnerOrSharedWithFullAccess
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
from adventures.utils import pagination from adventures.utils import pagination
@ -35,13 +35,13 @@ class AdventureViewSet(viewsets.ModelViewSet):
if not user.is_authenticated: if not user.is_authenticated:
if self.action in public_allowed_actions: if self.action in public_allowed_actions:
return Adventure.objects.retrieve_adventures( return Location.objects.retrieve_adventures(
user, include_public=True user, include_public=True
).order_by('-updated_at') ).order_by('-updated_at')
return Adventure.objects.none() return Location.objects.none()
include_public = self.action in public_allowed_actions include_public = self.action in public_allowed_actions
return Adventure.objects.retrieve_adventures( return Location.objects.retrieve_adventures(
user, user,
include_public=include_public, include_public=include_public,
include_owned=True, include_owned=True,
@ -116,7 +116,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
# Use the current user as owner since ManyToMany allows multiple collection owners # Use the current user as owner since ManyToMany allows multiple collection owners
user_to_assign = self.request.user user_to_assign = self.request.user
serializer.save(user_id=user_to_assign) serializer.save(user=user_to_assign)
def perform_update(self, serializer): def perform_update(self, serializer):
"""Update adventure.""" """Update adventure."""
@ -153,12 +153,12 @@ class AdventureViewSet(viewsets.ModelViewSet):
# Handle 'all' types # Handle 'all' types
if 'all' in types: if 'all' in types:
types = Category.objects.filter( types = Category.objects.filter(
user_id=request.user user=request.user
).values_list('name', flat=True) ).values_list('name', flat=True)
else: else:
# Validate provided types # Validate provided types
if not types or not all( if not types or not all(
Category.objects.filter(user_id=request.user, name=type_name).exists() Category.objects.filter(user=request.user, name=type_name).exists()
for type_name in types for type_name in types
): ):
return Response( return Response(
@ -167,9 +167,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
) )
# Build base queryset # Build base queryset
queryset = Adventure.objects.filter( queryset = Location.objects.filter(
category__in=Category.objects.filter(name__in=types, user_id=request.user), category__in=Category.objects.filter(name__in=types, user=request.user),
user_id=request.user.id user=request.user.id
) )
# Apply visit status filtering # Apply visit status filtering
@ -187,12 +187,12 @@ class AdventureViewSet(viewsets.ModelViewSet):
include_collections = request.query_params.get('include_collections', 'false') == 'true' include_collections = request.query_params.get('include_collections', 'false') == 'true'
# Build queryset with collection filtering # Build queryset with collection filtering
base_filter = Q(user_id=request.user.id) base_filter = Q(user=request.user.id)
if include_collections: if include_collections:
queryset = Adventure.objects.filter(base_filter) queryset = Location.objects.filter(base_filter)
else: else:
queryset = Adventure.objects.filter(base_filter, collections__isnull=True) queryset = Location.objects.filter(base_filter, collections__isnull=True)
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
@ -225,7 +225,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
def _validate_collection_permissions(self, collections): def _validate_collection_permissions(self, collections):
"""Validate user has permission to use all provided collections. Only the owner or shared users can use collections.""" """Validate user has permission to use all provided collections. Only the owner or shared users can use collections."""
for collection in collections: for collection in collections:
if not (collection.user_id == self.request.user or if not (collection.user == self.request.user or
collection.shared_with.filter(uuid=self.request.user.uuid).exists()): collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied( raise PermissionDenied(
f"You do not have permission to use collection '{collection.name}'." f"You do not have permission to use collection '{collection.name}'."
@ -235,7 +235,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
"""Validate permissions for collection updates (add/remove).""" """Validate permissions for collection updates (add/remove)."""
# Check permissions for new collections being added # Check permissions for new collections being added
for collection in new_collections: for collection in new_collections:
if (collection.user_id != self.request.user and if (collection.user != self.request.user and
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()): not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied( raise PermissionDenied(
f"You do not have permission to use collection '{collection.name}'." f"You do not have permission to use collection '{collection.name}'."
@ -247,7 +247,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
collections_to_remove = current_collections - new_collections_set collections_to_remove = current_collections - new_collections_set
for collection in collections_to_remove: for collection in collections_to_remove:
if (collection.user_id != self.request.user and if (collection.user != self.request.user and
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()): not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied( raise PermissionDenied(
f"You cannot remove the adventure from collection '{collection.name}' " f"You cannot remove the adventure from collection '{collection.name}' "
@ -284,7 +284,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
return True return True
# Check ownership # Check ownership
if user.is_authenticated and adventure.user_id == user: if user.is_authenticated and adventure.user == user:
return True return True
# Check shared collection access # Check shared collection access

View file

@ -2,7 +2,7 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Adventure, Attachment from adventures.models import Location, Attachment
from adventures.serializers import AttachmentSerializer from adventures.serializers import AttachmentSerializer
class AttachmentViewSet(viewsets.ModelViewSet): class AttachmentViewSet(viewsets.ModelViewSet):
@ -10,7 +10,7 @@ class AttachmentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return Attachment.objects.filter(user_id=self.request.user) return Attachment.objects.filter(user=self.request.user)
@action(detail=True, methods=['post']) @action(detail=True, methods=['post'])
def attachment_delete(self, request, *args, **kwargs): def attachment_delete(self, request, *args, **kwargs):
@ -19,38 +19,38 @@ class AttachmentViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure') location_id = request.data.get('location')
try: try:
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
except Adventure.DoesNotExist: except Location.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND) return Response({"error": "Location not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user: if location.user != request.user:
# Check if the adventure has any collections # Check if the location has any collections
if adventure.collections.exists(): if location.collections.exists():
# Check if the user is in the shared_with list of any of the adventure's collections # Check if the user is in the shared_with list of any of the location's collections
user_has_access = False user_has_access = False
for collection in adventure.collections.all(): for collection in location.collections.all():
if collection.shared_with.filter(id=request.user.id).exists(): if collection.shared_with.filter(id=request.user.id).exists():
user_has_access = True user_has_access = True
break break
if not user_has_access: if not user_has_access:
return Response({"error": "User does not have permission to access this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not have permission to access this location"}, status=status.HTTP_403_FORBIDDEN)
else: else:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().create(request, *args, **kwargs) return super().create(request, *args, **kwargs)
def perform_create(self, serializer): def perform_create(self, serializer):
adventure_id = self.request.data.get('adventure') location_id = self.request.data.get('location')
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
# If the adventure belongs to collections, set the owner to the collection owner # If the location belongs to collections, set the owner to the collection owner
if adventure.collections.exists(): if location.collections.exists():
# Get the first collection's owner (assuming all collections have the same owner) # Get the first collection's owner (assuming all collections have the same owner)
collection = adventure.collections.first() collection = location.collections.first()
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
else: else:
# Otherwise, set the owner to the request user # Otherwise, set the owner to the request user
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -2,7 +2,7 @@ from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Category, Adventure from adventures.models import Category, Location
from adventures.serializers import CategorySerializer from adventures.serializers import CategorySerializer
class CategoryViewSet(viewsets.ModelViewSet): class CategoryViewSet(viewsets.ModelViewSet):
@ -11,7 +11,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return Category.objects.filter(user_id=self.request.user) return Category.objects.filter(user=self.request.user)
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def categories(self, request): def categories(self, request):
@ -24,7 +24,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
instance = self.get_object() instance = self.get_object()
if instance.user_id != request.user: if instance.user != request.user:
return Response({"error": "User does not own this category"}, status return Response({"error": "User does not own this category"}, status
=400) =400)
@ -32,11 +32,11 @@ class CategoryViewSet(viewsets.ModelViewSet):
return Response({"error": "Cannot delete the general category"}, status=400) return Response({"error": "Cannot delete the general category"}, status=400)
# set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user # set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user
general_category = Category.objects.filter(user_id=request.user, name='general').first() general_category = Category.objects.filter(user=request.user, name='general').first()
if not general_category: if not general_category:
general_category = Category.objects.create(user_id=request.user, name='general', icon='🌍', display_name='General') general_category = Category.objects.create(user=request.user, name='general', icon='🌍', display_name='General')
Adventure.objects.filter(category=instance).update(category=general_category) Location.objects.filter(category=instance).update(category=general_category)
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)

View file

@ -24,7 +24,7 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Checklist.objects.filter( queryset = Checklist.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -41,12 +41,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures # For individual adventure retrieval, include public adventures
return Checklist.objects.filter( return Checklist.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
else: else:
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own adventures and shared adventures
return Checklist.objects.filter( return Checklist.objects.filter(
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -65,11 +65,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -94,11 +94,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -119,12 +119,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -4,7 +4,7 @@ from django.db import transaction
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Collection, Adventure, Transportation, Note, Checklist from adventures.models import Collection, Location, Transportation, Note, Checklist
from adventures.permissions import CollectionShared from adventures.permissions import CollectionShared
from adventures.serializers import CollectionSerializer from adventures.serializers import CollectionSerializer
from users.models import CustomUser as User from users.models import CustomUser as User
@ -16,7 +16,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
pagination_class = pagination.StandardResultsSetPagination pagination_class = pagination.StandardResultsSetPagination
# def get_queryset(self): # def get_queryset(self):
# return Collection.objects.filter(Q(user_id=self.request.user.id) & Q(is_archived=False)) # return Collection.objects.filter(Q(user=self.request.user.id) & Q(is_archived=False))
def apply_sorting(self, queryset): def apply_sorting(self, queryset):
order_by = self.request.query_params.get('order_by', 'name') order_by = self.request.query_params.get('order_by', 'name')
@ -55,7 +55,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
# make sure the user is authenticated # make sure the user is authenticated
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(user_id=request.user.id, is_archived=False) queryset = Collection.objects.filter(user=request.user.id, is_archived=False)
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
collections = self.paginate_and_respond(queryset, request) collections = self.paginate_and_respond(queryset, request)
return collections return collections
@ -66,7 +66,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter( queryset = Collection.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
@ -80,7 +80,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter( queryset = Collection.objects.filter(
Q(user_id=request.user.id) & Q(is_archived=True) Q(user=request.user.id) & Q(is_archived=True)
) )
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
@ -99,7 +99,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
if 'collection' in serializer.validated_data: if 'collection' in serializer.validated_data:
new_collection = serializer.validated_data['collection'] new_collection = serializer.validated_data['collection']
# if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error # if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error
if new_collection != instance.collection and new_collection.user_id != request.user: if new_collection != instance.collection and new_collection.user != request.user:
return Response({"error": "User does not own the new collection"}, status=400) return Response({"error": "User does not own the new collection"}, status=400)
# Check if the 'is_public' field is present in the update data # Check if the 'is_public' field is present in the update data
@ -107,12 +107,12 @@ class CollectionViewSet(viewsets.ModelViewSet):
new_public_status = serializer.validated_data['is_public'] new_public_status = serializer.validated_data['is_public']
# if is_public has changed and the user is not the owner of the collection return an error # if is_public has changed and the user is not the owner of the collection return an error
if new_public_status != instance.is_public and instance.user_id != request.user: if new_public_status != instance.is_public and instance.user != request.user:
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user_id}") print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user}")
return Response({"error": "User does not own the collection"}, status=400) return Response({"error": "User does not own the collection"}, status=400)
# Get all adventures in this collection # Get all adventures in this collection
adventures_in_collection = Adventure.objects.filter(collections=instance) adventures_in_collection = Location.objects.filter(collections=instance)
if new_public_status: if new_public_status:
# If collection becomes public, make all adventures public # If collection becomes public, make all adventures public
@ -129,7 +129,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
adventure_ids_to_set_private.append(adventure.id) adventure_ids_to_set_private.append(adventure.id)
# Bulk update those adventures # Bulk update those adventures
Adventure.objects.filter(id__in=adventure_ids_to_set_private).update(is_public=False) Location.objects.filter(id__in=adventure_ids_to_set_private).update(is_public=False)
# Update transportations, notes, and checklists related to this collection # Update transportations, notes, and checklists related to this collection
# These still use direct ForeignKey relationships # These still use direct ForeignKey relationships
@ -207,28 +207,28 @@ class CollectionViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
if self.action == 'destroy': if self.action == 'destroy':
return Collection.objects.filter(user_id=self.request.user.id) return Collection.objects.filter(user=self.request.user.id)
if self.action in ['update', 'partial_update']: if self.action in ['update', 'partial_update']:
return Collection.objects.filter( return Collection.objects.filter(
Q(user_id=self.request.user.id) | Q(shared_with=self.request.user) Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct() ).distinct()
if self.action == 'retrieve': if self.action == 'retrieve':
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return Collection.objects.filter(is_public=True) return Collection.objects.filter(is_public=True)
return Collection.objects.filter( return Collection.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct() ).distinct()
# For list action, include collections owned by the user or shared with the user, that are not archived # For list action, include collections owned by the user or shared with the user, that are not archived
return Collection.objects.filter( return Collection.objects.filter(
(Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False) (Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
).distinct() ).distinct()
def perform_create(self, serializer): def perform_create(self, serializer):
# This is ok because you cannot share a collection when creating it # This is ok because you cannot share a collection when creating it
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)
def paginate_and_respond(self, queryset, request): def paginate_and_respond(self, queryset, request):
paginator = self.pagination_class() paginator = self.pagination_class()

View file

@ -3,7 +3,7 @@ from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from django.db.models import Q from django.db.models import Q
from django.contrib.postgres.search import SearchVector, SearchQuery from django.contrib.postgres.search import SearchVector, SearchQuery
from adventures.models import Adventure, Collection from adventures.models import Location, Collection
from adventures.serializers import AdventureSerializer, CollectionSerializer from adventures.serializers import AdventureSerializer, CollectionSerializer
from worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion from worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer
@ -31,14 +31,14 @@ class GlobalSearchView(viewsets.ViewSet):
} }
# Adventures: Full-Text Search # Adventures: Full-Text Search
adventures = Adventure.objects.annotate( adventures = Location.objects.annotate(
search=SearchVector('name', 'description', 'location') search=SearchVector('name', 'description', 'location')
).filter(search=SearchQuery(search_term), user_id=request.user) ).filter(search=SearchQuery(search_term), user=request.user)
results["adventures"] = AdventureSerializer(adventures, many=True).data results["adventures"] = AdventureSerializer(adventures, many=True).data
# Collections: Partial Match Search # Collections: Partial Match Search
collections = Collection.objects.filter( collections = Collection.objects.filter(
Q(name__icontains=search_term) & Q(user_id=request.user) Q(name__icontains=search_term) & Q(user=request.user)
) )
results["collections"] = CollectionSerializer(collections, many=True).data results["collections"] = CollectionSerializer(collections, many=True).data
@ -64,10 +64,10 @@ class GlobalSearchView(viewsets.ViewSet):
results["cities"] = CitySerializer(cities, many=True).data results["cities"] = CitySerializer(cities, many=True).data
# Visited Regions and Cities # Visited Regions and Cities
visited_regions = VisitedRegion.objects.filter(user_id=request.user) visited_regions = VisitedRegion.objects.filter(user=request.user)
results["visited_regions"] = VisitedRegionSerializer(visited_regions, many=True).data results["visited_regions"] = VisitedRegionSerializer(visited_regions, many=True).data
visited_cities = VisitedCity.objects.filter(user_id=request.user) visited_cities = VisitedCity.objects.filter(user=request.user)
results["visited_cities"] = VisitedCitySerializer(visited_cities, many=True).data results["visited_cities"] = VisitedCitySerializer(visited_cities, many=True).data
return Response(results) return Response(results)

View file

@ -4,7 +4,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from icalendar import Calendar, Event, vText, vCalAddress from icalendar import Calendar, Event, vText, vCalAddress
from datetime import datetime, timedelta from datetime import datetime, timedelta
from adventures.models import Adventure from adventures.models import Location
from adventures.serializers import AdventureSerializer from adventures.serializers import AdventureSerializer
class IcsCalendarGeneratorViewSet(viewsets.ViewSet): class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
@ -12,11 +12,10 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def generate(self, request): def generate(self, request):
adventures = Adventure.objects.filter(user_id=request.user) adventures = Location.objects.filter(user=request.user)
serializer = AdventureSerializer(adventures, many=True) serializer = AdventureSerializer(adventures, many=True)
user = request.user user = request.user
name = f"{user.first_name} {user.last_name}" name = f"{user.first_name} {user.last_name}"
print(serializer.data)
cal = Calendar() cal = Calendar()
cal.add('prodid', '-//My Adventure Calendar//example.com//') cal.add('prodid', '-//My Adventure Calendar//example.com//')

View file

@ -17,7 +17,7 @@ class LodgingViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response(status=status.HTTP_403_FORBIDDEN) return Response(status=status.HTTP_403_FORBIDDEN)
queryset = Lodging.objects.filter( queryset = Lodging.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -27,11 +27,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures # For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
return Lodging.objects.filter( return Lodging.objects.filter(
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own adventures and shared adventures
return Lodging.objects.filter( return Lodging.objects.filter(
Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -48,11 +48,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection != instance.collection: if new_collection is not None and new_collection != instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -73,12 +73,12 @@ class LodgingViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -24,7 +24,7 @@ class NoteViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Note.objects.filter( queryset = Note.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -41,12 +41,12 @@ class NoteViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures # For individual adventure retrieval, include public adventures
return Note.objects.filter( return Note.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
else: else:
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own adventures and shared adventures
return Note.objects.filter( return Note.objects.filter(
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -65,11 +65,11 @@ class NoteViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -94,11 +94,11 @@ class NoteViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -119,12 +119,12 @@ class NoteViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -3,7 +3,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from worldtravel.models import Region, City, VisitedRegion, VisitedCity from worldtravel.models import Region, City, VisitedRegion, VisitedCity
from adventures.models import Adventure from adventures.models import Location
from adventures.serializers import AdventureSerializer from adventures.serializers import AdventureSerializer
import requests import requests
from adventures.geocoding import reverse_geocode from adventures.geocoding import reverse_geocode
@ -52,7 +52,7 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
new_regions = {} new_regions = {}
new_city_count = 0 new_city_count = 0
new_cities = {} new_cities = {}
adventures = Adventure.objects.filter(user_id=self.request.user) adventures = Location.objects.filter(user=self.request.user)
serializer = AdventureSerializer(adventures, many=True) serializer = AdventureSerializer(adventures, many=True)
for adventure, serialized_adventure in zip(adventures, serializer.data): for adventure, serialized_adventure in zip(adventures, serializer.data):
if serialized_adventure['is_visited'] == True: if serialized_adventure['is_visited'] == True:
@ -69,18 +69,18 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
# data already contains region_id and city_id # data already contains region_id and city_id
if 'region_id' in data and data['region_id'] is not None: if 'region_id' in data and data['region_id'] is not None:
region = Region.objects.filter(id=data['region_id']).first() region = Region.objects.filter(id=data['region_id']).first()
visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first() visited_region = VisitedRegion.objects.filter(region=region, user=self.request.user).first()
if not visited_region: if not visited_region:
visited_region = VisitedRegion(region=region, user_id=self.request.user) visited_region = VisitedRegion(region=region, user=self.request.user)
visited_region.save() visited_region.save()
new_region_count += 1 new_region_count += 1
new_regions[region.id] = region.name new_regions[region.id] = region.name
if 'city_id' in data and data['city_id'] is not None: if 'city_id' in data and data['city_id'] is not None:
city = City.objects.filter(id=data['city_id']).first() city = City.objects.filter(id=data['city_id']).first()
visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first() visited_city = VisitedCity.objects.filter(city=city, user=self.request.user).first()
if not visited_city: if not visited_city:
visited_city = VisitedCity(city=city, user_id=self.request.user) visited_city = VisitedCity(city=city, user=self.request.user)
visited_city.save() visited_city.save()
new_city_count += 1 new_city_count += 1
new_cities[city.id] = city.name new_cities[city.id] = city.name

View file

@ -4,7 +4,7 @@ from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion
from adventures.models import Adventure, Collection from adventures.models import Location, Collection
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -26,18 +26,18 @@ class StatsViewSet(viewsets.ViewSet):
user.email = None user.email = None
# get the counts for the user # get the counts for the user
adventure_count = Adventure.objects.filter( adventure_count = Location.objects.filter(
user_id=user.id).count() user=user.id).count()
trips_count = Collection.objects.filter( trips_count = Collection.objects.filter(
user_id=user.id).count() user=user.id).count()
visited_city_count = VisitedCity.objects.filter( visited_city_count = VisitedCity.objects.filter(
user_id=user.id).count() user=user.id).count()
total_cities = City.objects.count() total_cities = City.objects.count()
visited_region_count = VisitedRegion.objects.filter( visited_region_count = VisitedRegion.objects.filter(
user_id=user.id).count() user=user.id).count()
total_regions = Region.objects.count() total_regions = Region.objects.count()
visited_country_count = VisitedRegion.objects.filter( visited_country_count = VisitedRegion.objects.filter(
user_id=user.id).values('region__country').distinct().count() user=user.id).values('region__country').distinct().count()
total_countries = Country.objects.count() total_countries = Country.objects.count()
return Response({ return Response({
'adventure_count': adventure_count, 'adventure_count': adventure_count,

View file

@ -17,7 +17,7 @@ class TransportationViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response(status=status.HTTP_403_FORBIDDEN) return Response(status=status.HTTP_403_FORBIDDEN)
queryset = Transportation.objects.filter( queryset = Transportation.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -27,11 +27,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures # For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
return Transportation.objects.filter( return Transportation.objects.filter(
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own adventures and shared adventures
return Transportation.objects.filter( return Transportation.objects.filter(
Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -48,11 +48,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection != instance.collection: if new_collection is not None and new_collection != instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -73,12 +73,12 @@ class TransportationViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated
import requests import requests
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from django.conf import settings from django.conf import settings
from adventures.models import AdventureImage from adventures.models import LocationImage
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
import logging import logging
@ -253,11 +253,11 @@ class ImmichIntegrationView(viewsets.ViewSet):
""" """
GET an Immich image using the integration and asset ID. GET an Immich image using the integration and asset ID.
Access levels (in order of priority): Access levels (in order of priority):
1. Public adventures: accessible by anyone 1. Public locations: accessible by anyone
2. Private adventures in public collections: accessible by anyone 2. Private locations in public collections: accessible by anyone
3. Private adventures in private collections shared with user: accessible by shared users 3. Private locations in private collections shared with user: accessible by shared users
4. Private adventures: accessible only to the owner 4. Private locations: accessible only to the owner
5. No AdventureImage: owner can still view via integration 5. No LocationImage: owner can still view via integration
""" """
if not imageid or not integration_id: if not imageid or not integration_id:
return Response({ return Response({
@ -268,31 +268,31 @@ class ImmichIntegrationView(viewsets.ViewSet):
# Lookup integration and user # Lookup integration and user
integration = get_object_or_404(ImmichIntegration, id=integration_id) integration = get_object_or_404(ImmichIntegration, id=integration_id)
owner_id = integration.user_id owner_id = integration.user
# Try to find the image entry with collections and sharing information # Try to find the image entry with collections and sharing information
image_entry = ( image_entry = (
AdventureImage.objects LocationImage.objects
.filter(immich_id=imageid, user_id=owner_id) .filter(immich_id=imageid, user=owner_id)
.select_related('adventure') .select_related('location')
.prefetch_related('adventure__collections', 'adventure__collections__shared_with') .prefetch_related('location__collections', 'location__collections__shared_with')
.order_by('-adventure__is_public') # Public adventures first .order_by('-location__is_public') # Public locations first
.first() .first()
) )
# Access control # Access control
if image_entry: if image_entry:
adventure = image_entry.adventure location = image_entry.location
collections = adventure.collections.all() collections = location.collections.all()
# Determine access level # Determine access level
is_authorized = False is_authorized = False
# Level 1: Public adventure (highest priority) # Level 1: Public location (highest priority)
if adventure.is_public: if location.is_public:
is_authorized = True is_authorized = True
# Level 2: Private adventure in any public collection # Level 2: Private location in any public collection
elif any(collection.is_public for collection in collections): elif any(collection.is_public for collection in collections):
is_authorized = True is_authorized = True
@ -308,15 +308,15 @@ class ImmichIntegrationView(viewsets.ViewSet):
if not is_authorized: if not is_authorized:
return Response({ return Response({
'message': 'This image belongs to a private adventure and you are not authorized.', 'message': 'This image belongs to a private location and you are not authorized.',
'error': True, 'error': True,
'code': 'immich.permission_denied' 'code': 'immich.permission_denied'
}, status=status.HTTP_403_FORBIDDEN) }, status=status.HTTP_403_FORBIDDEN)
else: else:
# No AdventureImage exists; allow only the integration owner # No LocationImage exists; allow only the integration owner
if not request.user.is_authenticated or request.user.id != owner_id: if not request.user.is_authenticated or request.user.id != owner_id:
return Response({ return Response({
'message': 'Image is not linked to any adventure and you are not the owner.', 'message': 'Image is not linked to any location and you are not the owner.',
'error': True, 'error': True,
'code': 'immich.not_found' 'code': 'immich.not_found'
}, status=status.HTTP_404_NOT_FOUND) }, status=status.HTTP_404_NOT_FOUND)

View file

@ -6,5 +6,5 @@ def get_user_uuid(user):
class CustomModelSerializer(serializers.ModelSerializer): class CustomModelSerializer(serializers.ModelSerializer):
def to_representation(self, instance): def to_representation(self, instance):
representation = super().to_representation(instance) representation = super().to_representation(instance)
representation['user_id'] = get_user_uuid(instance.user_id) representation['user'] = get_user_uuid(instance.user)
return representation return representation

View file

@ -12,7 +12,7 @@ from django.contrib.auth import get_user_model
from .serializers import CustomUserDetailsSerializer as PublicUserSerializer from .serializers import CustomUserDetailsSerializer as PublicUserSerializer
from allauth.socialaccount.models import SocialApp from allauth.socialaccount.models import SocialApp
from adventures.serializers import AdventureSerializer, CollectionSerializer from adventures.serializers import AdventureSerializer, CollectionSerializer
from adventures.models import Adventure, Collection from adventures.models import Location, Collection
from allauth.socialaccount.models import SocialAccount from allauth.socialaccount.models import SocialAccount
User = get_user_model() User = get_user_model()
@ -99,8 +99,8 @@ class PublicUserDetailView(APIView):
user.email = None user.email = None
# Get the users adventures and collections to include in the response # Get the users adventures and collections to include in the response
adventures = Adventure.objects.filter(user_id=user, is_public=True) adventures = Location.objects.filter(user=user, is_public=True)
collections = Collection.objects.filter(user_id=user, is_public=True) collections = Collection.objects.filter(user=user, is_public=True)
adventure_serializer = AdventureSerializer(adventures, many=True) adventure_serializer = AdventureSerializer(adventures, many=True)
collection_serializer = CollectionSerializer(collections, many=True) collection_serializer = CollectionSerializer(collections, many=True)

View file

@ -1,12 +1,12 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from adventures.models import Adventure from adventures.models import Location
import time import time
class Command(BaseCommand): class Command(BaseCommand):
help = 'Bulk geocode all adventures by triggering save on each one' help = 'Bulk geocode all adventures by triggering save on each one'
def handle(self, *args, **options): def handle(self, *args, **options):
adventures = Adventure.objects.all() adventures = Location.objects.all()
total = adventures.count() total = adventures.count()
self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures')) self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures'))

View file

@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-19 20:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('worldtravel', '0016_remove_city_insert_id_remove_country_insert_id_and_more'),
]
operations = [
migrations.RenameField(
model_name='visitedregion',
old_name='user_id',
new_name='user',
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-19 20:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('worldtravel', '0017_rename_user_id_visitedregion_user'),
]
operations = [
migrations.RenameField(
model_name='visitedcity',
old_name='user_id',
new_name='user',
),
]

View file

@ -6,7 +6,7 @@ from django.contrib.gis.db import models as gis_models
User = get_user_model() User = get_user_model()
default_user_id = 1 # Replace with an actual user ID default_user = 1 # Replace with an actual user ID
class Country(models.Model): class Country(models.Model):
@ -50,29 +50,29 @@ class City(models.Model):
class VisitedRegion(models.Model): class VisitedRegion(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
region = models.ForeignKey(Region, on_delete=models.CASCADE) region = models.ForeignKey(Region, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return f'{self.region.name} ({self.region.country.country_code}) visited by: {self.user_id.username}' return f'{self.region.name} ({self.region.country.country_code}) visited by: {self.user.username}'
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if VisitedRegion.objects.filter(user_id=self.user_id, region=self.region).exists(): if VisitedRegion.objects.filter(user=self.user, region=self.region).exists():
raise ValidationError("Region already visited by user.") raise ValidationError("Region already visited by user.")
super().save(*args, **kwargs) super().save(*args, **kwargs)
class VisitedCity(models.Model): class VisitedCity(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
city = models.ForeignKey(City, on_delete=models.CASCADE) city = models.ForeignKey(City, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return f'{self.city.name} ({self.city.region.name}) visited by: {self.user_id.username}' return f'{self.city.name} ({self.city.region.name}) visited by: {self.user.username}'
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if VisitedCity.objects.filter(user_id=self.user_id, city=self.city).exists(): if VisitedCity.objects.filter(user=self.user, city=self.city).exists():
raise ValidationError("City already visited by user.") raise ValidationError("City already visited by user.")
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -25,7 +25,7 @@ class CountrySerializer(serializers.ModelSerializer):
user = getattr(request, 'user', None) user = getattr(request, 'user', None)
if user and user.is_authenticated: if user and user.is_authenticated:
return VisitedRegion.objects.filter(region__country=obj, user_id=user).count() return VisitedRegion.objects.filter(region__country=obj, user=user).count()
return 0 return 0
@ -62,8 +62,8 @@ class VisitedRegionSerializer(CustomModelSerializer):
class Meta: class Meta:
model = VisitedRegion model = VisitedRegion
fields = ['id', 'user_id', 'region', 'longitude', 'latitude', 'name'] fields = ['id', 'user', 'region', 'longitude', 'latitude', 'name']
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name'] read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']
class VisitedCitySerializer(CustomModelSerializer): class VisitedCitySerializer(CustomModelSerializer):
longitude = serializers.DecimalField(source='city.longitude', max_digits=9, decimal_places=6, read_only=True) longitude = serializers.DecimalField(source='city.longitude', max_digits=9, decimal_places=6, read_only=True)
@ -72,5 +72,5 @@ class VisitedCitySerializer(CustomModelSerializer):
class Meta: class Meta:
model = VisitedCity model = VisitedCity
fields = ['id', 'user_id', 'city', 'longitude', 'latitude', 'name'] fields = ['id', 'user', 'city', 'longitude', 'latitude', 'name']
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name'] read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']

View file

@ -13,7 +13,7 @@ from django.contrib.gis.geos import Point
from django.conf import settings from django.conf import settings
from rest_framework.decorators import action from rest_framework.decorators import action
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from adventures.models import Adventure from adventures.models import Location
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@ -28,7 +28,7 @@ def regions_by_country(request, country_code):
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def visits_by_country(request, country_code): def visits_by_country(request, country_code):
country = get_object_or_404(Country, country_code=country_code) country = get_object_or_404(Country, country_code=country_code)
visits = VisitedRegion.objects.filter(region__country=country, user_id=request.user.id) visits = VisitedRegion.objects.filter(region__country=country, user=request.user.id)
serializer = VisitedRegionSerializer(visits, many=True) serializer = VisitedRegionSerializer(visits, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -45,7 +45,7 @@ def cities_by_region(request, region_id):
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def visits_by_region(request, region_id): def visits_by_region(request, region_id):
region = get_object_or_404(Region, id=region_id) region = get_object_or_404(Region, id=region_id)
visits = VisitedCity.objects.filter(city__region=region, user_id=request.user.id) visits = VisitedCity.objects.filter(city__region=region, user=request.user.id)
serializer = VisitedCitySerializer(visits, many=True) serializer = VisitedCitySerializer(visits, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -71,7 +71,7 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet):
# make a post action that will get all of the users adventures and check if the point is in any of the regions if so make a visited region object for that user if it does not already exist # make a post action that will get all of the users adventures and check if the point is in any of the regions if so make a visited region object for that user if it does not already exist
@action(detail=False, methods=['post']) @action(detail=False, methods=['post'])
def region_check_all_adventures(self, request): def region_check_all_adventures(self, request):
adventures = Adventure.objects.filter(user_id=request.user.id, type='visited') adventures = Location.objects.filter(user=request.user.id, type='visited')
count = 0 count = 0
for adventure in adventures: for adventure in adventures:
if adventure.latitude is not None and adventure.longitude is not None: if adventure.latitude is not None and adventure.longitude is not None:
@ -79,8 +79,8 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet):
point = Point(float(adventure.longitude), float(adventure.latitude), srid=4326) point = Point(float(adventure.longitude), float(adventure.latitude), srid=4326)
region = Region.objects.filter(geometry__contains=point).first() region = Region.objects.filter(geometry__contains=point).first()
if region: if region:
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists(): if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
VisitedRegion.objects.create(user_id=request.user, region=region) VisitedRegion.objects.create(user=request.user, region=region)
count += 1 count += 1
except Exception as e: except Exception as e:
print(f"Error processing adventure {adventure.id}: {e}") print(f"Error processing adventure {adventure.id}: {e}")
@ -97,14 +97,14 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return VisitedRegion.objects.filter(user_id=self.request.user.id) return VisitedRegion.objects.filter(user=self.request.user.id)
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
request.data['user_id'] = request.user request.data['user'] = request.user
if VisitedRegion.objects.filter(user_id=request.user.id, region=request.data['region']).exists(): if VisitedRegion.objects.filter(user=request.user.id, region=request.data['region']).exists():
return Response({"error": "Region already visited by user."}, status=400) return Response({"error": "Region already visited by user."}, status=400)
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -115,7 +115,7 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
def destroy(self, request, **kwargs): def destroy(self, request, **kwargs):
# delete by region id # delete by region id
region = get_object_or_404(Region, id=kwargs['pk']) region = get_object_or_404(Region, id=kwargs['pk'])
visited_region = VisitedRegion.objects.filter(user_id=request.user.id, region=region) visited_region = VisitedRegion.objects.filter(user=request.user.id, region=region)
if visited_region.exists(): if visited_region.exists():
visited_region.delete() visited_region.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -127,27 +127,27 @@ class VisitedCityViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return VisitedCity.objects.filter(user_id=self.request.user.id) return VisitedCity.objects.filter(user=self.request.user.id)
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
request.data['user_id'] = request.user request.data['user'] = request.user
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
self.perform_create(serializer) self.perform_create(serializer)
# if the region is not visited, visit it # if the region is not visited, visit it
region = serializer.validated_data['city'].region region = serializer.validated_data['city'].region
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists(): if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
VisitedRegion.objects.create(user_id=request.user, region=region) VisitedRegion.objects.create(user=request.user, region=region)
headers = self.get_success_headers(serializer.data) headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def destroy(self, request, **kwargs): def destroy(self, request, **kwargs):
# delete by city id # delete by city id
city = get_object_or_404(City, id=kwargs['pk']) city = get_object_or_404(City, id=kwargs['pk'])
visited_city = VisitedCity.objects.filter(user_id=request.user.id, city=city) visited_city = VisitedCity.objects.filter(user=request.user.id, city=city)
if visited_city.exists(): if visited_city.exists():
visited_city.delete() visited_city.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)

View file

@ -280,7 +280,7 @@
{$t('adventures.open_details')} {$t('adventures.open_details')}
</button> </button>
{#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if adventure.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-square btn-sm btn-base-300"> <div tabindex="0" role="button" class="btn btn-square btn-sm btn-base-300">
<DotsHorizontal class="w-5 h-5" /> <DotsHorizontal class="w-5 h-5" />
@ -297,7 +297,7 @@
</button> </button>
</li> </li>
{#if user?.uuid == adventure.user_id} {#if user?.uuid == adventure.user}
<li> <li>
<button <button
on:click={() => (isCollectionModalOpen = true)} on:click={() => (isCollectionModalOpen = true)}

View file

@ -110,13 +110,13 @@
longitude: NaN, longitude: NaN,
location: null, location: null,
images: [], images: [],
user_id: null, user: null,
category: { category: {
id: '', id: '',
name: '', name: '',
display_name: '', display_name: '',
icon: '', icon: '',
user_id: '' user: ''
}, },
attachments: [] attachments: []
}; };
@ -135,7 +135,7 @@
longitude: adventureToEdit?.longitude || NaN, longitude: adventureToEdit?.longitude || NaN,
location: adventureToEdit?.location || null, location: adventureToEdit?.location || null,
images: adventureToEdit?.images || [], images: adventureToEdit?.images || [],
user_id: adventureToEdit?.user_id || null, user: adventureToEdit?.user || null,
visits: adventureToEdit?.visits || [], visits: adventureToEdit?.visits || [],
is_visited: adventureToEdit?.is_visited || false, is_visited: adventureToEdit?.is_visited || false,
category: adventureToEdit?.category || { category: adventureToEdit?.category || {
@ -143,7 +143,7 @@
name: '', name: '',
display_name: '', display_name: '',
icon: '', icon: '',
user_id: '' user: ''
}, },
attachments: adventureToEdit?.attachments || [] attachments: adventureToEdit?.attachments || []
@ -245,7 +245,7 @@
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('adventure', adventure.id); formData.append('location', adventure.id);
formData.append('name', attachmentName); formData.append('name', attachmentName);
try { try {
@ -451,7 +451,7 @@
name: 'general', name: 'general',
display_name: 'General', display_name: 'General',
icon: '🌍', icon: '🌍',
user_id: '' user: ''
}; };
} }
} }

View file

@ -10,7 +10,7 @@
display_name: '', display_name: '',
icon: '', icon: '',
id: '', id: '',
user_id: '', user: '',
num_adventures: 0 num_adventures: 0
}; };

View file

@ -99,7 +99,7 @@
<Launch class="w-5 h-5" /> <Launch class="w-5 h-5" />
{$t('notes.open')} {$t('notes.open')}
</button> </button>
{#if checklist.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if checklist.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<button <button
id="delete_adventure" id="delete_adventure"
data-umami-event="Delete Checklist" data-umami-event="Delete Checklist"

View file

@ -19,7 +19,7 @@
let warning: string | null = ''; let warning: string | null = '';
let isReadOnly = let isReadOnly =
!(checklist && user?.uuid == checklist?.user_id) && !(checklist && user?.uuid == checklist?.user) &&
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) && !(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
!!checklist; !!checklist;
let newStatus: boolean = false; let newStatus: boolean = false;
@ -40,7 +40,7 @@
name: newItem, name: newItem,
is_checked: newStatus, is_checked: newStatus,
id: '', id: '',
user_id: '', user: '',
checklist: 0, checklist: 0,
created_at: '', created_at: '',
updated_at: '' updated_at: ''

View file

@ -170,7 +170,7 @@
<Launch class="w-4 h-4" /> <Launch class="w-4 h-4" />
{$t('adventures.open_details')} {$t('adventures.open_details')}
</button> </button>
{#if user && user.uuid == collection.user_id} {#if user && user.uuid == collection.user}
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<button type="button" class="btn btn-square btn-sm btn-base-300"> <button type="button" class="btn btn-square btn-sm btn-base-300">
<DotsHorizontal class="w-5 h-5" /> <DotsHorizontal class="w-5 h-5" />

View file

@ -17,7 +17,7 @@
description: collectionToEdit?.description || '', description: collectionToEdit?.description || '',
start_date: collectionToEdit?.start_date || null, start_date: collectionToEdit?.start_date || null,
end_date: collectionToEdit?.end_date || null, end_date: collectionToEdit?.end_date || null,
user_id: collectionToEdit?.user_id || '', user: collectionToEdit?.user || '',
is_public: collectionToEdit?.is_public || false, is_public: collectionToEdit?.is_public || false,
adventures: collectionToEdit?.adventures || [], adventures: collectionToEdit?.adventures || [],
link: collectionToEdit?.link || '', link: collectionToEdit?.link || '',

View file

@ -156,7 +156,7 @@
</div> </div>
<!-- Reservation Info --> <!-- Reservation Info -->
{#if lodging.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if lodging.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="space-y-2"> <div class="space-y-2">
{#if lodging.reservation_number} {#if lodging.reservation_number}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -174,7 +174,7 @@
{/if} {/if}
<!-- Actions --> <!-- Actions -->
{#if lodging.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if lodging.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="pt-4 border-t border-base-300 flex justify-end gap-2"> <div class="pt-4 border-t border-base-300 flex justify-end gap-2">
<button <button
class="btn btn-neutral btn-sm flex items-center gap-1" class="btn btn-neutral btn-sm flex items-center gap-1"

View file

@ -31,7 +31,7 @@
function initializeLodging(lodgingToEdit: Lodging | null): Lodging { function initializeLodging(lodgingToEdit: Lodging | null): Lodging {
return { return {
id: lodgingToEdit?.id || '', id: lodgingToEdit?.id || '',
user_id: lodgingToEdit?.user_id || '', user: lodgingToEdit?.user || '',
name: lodgingToEdit?.name || '', name: lodgingToEdit?.name || '',
type: lodgingToEdit?.type || 'other', type: lodgingToEdit?.type || 'other',
description: lodgingToEdit?.description || '', description: lodgingToEdit?.description || '',

View file

@ -124,7 +124,7 @@
<Launch class="w-5 h-5" /> <Launch class="w-5 h-5" />
{$t('notes.open')} {$t('notes.open')}
</button> </button>
{#if note.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if note.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<button <button
id="delete_adventure" id="delete_adventure"
data-umami-event="Delete Adventure" data-umami-event="Delete Adventure"

View file

@ -20,7 +20,7 @@
let constrainDates: boolean = false; let constrainDates: boolean = false;
let isReadOnly = let isReadOnly =
!(note && user?.uuid == note?.user_id) && !(note && user?.uuid == note?.user) &&
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) && !(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
!!note; !!note;

View file

@ -192,7 +192,7 @@
</div> </div>
<!-- Actions --> <!-- Actions -->
{#if transportation.user_id === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if transportation.user === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="pt-4 border-t border-base-300 flex justify-end gap-2"> <div class="pt-4 border-t border-base-300 flex justify-end gap-2">
<button <button
class="btn btn-neutral btn-sm flex items-center gap-1" class="btn btn-neutral btn-sm flex items-center gap-1"

View file

@ -29,7 +29,7 @@
flight_number: transportationToEdit?.flight_number || '', flight_number: transportationToEdit?.flight_number || '',
from_location: transportationToEdit?.from_location || '', from_location: transportationToEdit?.from_location || '',
to_location: transportationToEdit?.to_location || '', to_location: transportationToEdit?.to_location || '',
user_id: transportationToEdit?.user_id || '', user: transportationToEdit?.user || '',
is_public: transportationToEdit?.is_public || false, is_public: transportationToEdit?.is_public || false,
collection: transportationToEdit?.collection || collection.id, collection: transportationToEdit?.collection || collection.id,
created_at: transportationToEdit?.created_at || '', created_at: transportationToEdit?.created_at || '',

View file

@ -16,7 +16,7 @@ export type User = {
export type Adventure = { export type Adventure = {
id: string; id: string;
user_id: string | null; user: string | null;
name: string; name: string;
location?: string | null; location?: string | null;
activity_types?: string[] | null; activity_types?: string[] | null;
@ -96,7 +96,7 @@ export type City = {
export type VisitedRegion = { export type VisitedRegion = {
id: number; id: number;
region: string; region: string;
user_id: string; user: string;
longitude: number; longitude: number;
latitude: number; latitude: number;
name: string; name: string;
@ -105,7 +105,7 @@ export type VisitedRegion = {
export type VisitedCity = { export type VisitedCity = {
id: number; id: number;
city: string; city: string;
user_id: string; user: string;
longitude: number; longitude: number;
latitude: number; latitude: number;
name: string; name: string;
@ -123,7 +123,7 @@ export type Point = {
export type Collection = { export type Collection = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
description: string; description: string;
is_public: boolean; is_public: boolean;
@ -153,7 +153,7 @@ export type GeocodeSearchResult = {
export type Transportation = { export type Transportation = {
id: string; id: string;
user_id: string; user: string;
type: string; type: string;
name: string; name: string;
description: string | null; description: string | null;
@ -179,7 +179,7 @@ export type Transportation = {
export type Note = { export type Note = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
content: string | null; content: string | null;
links: string[] | null; links: string[] | null;
@ -192,7 +192,7 @@ export type Note = {
export type Checklist = { export type Checklist = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
items: ChecklistItem[]; items: ChecklistItem[];
date: string | null; // ISO 8601 date string date: string | null; // ISO 8601 date string
@ -204,7 +204,7 @@ export type Checklist = {
export type ChecklistItem = { export type ChecklistItem = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
is_checked: boolean; is_checked: boolean;
checklist: number; checklist: number;
@ -235,7 +235,7 @@ export type Category = {
name: string; name: string;
display_name: string; display_name: string;
icon: string; icon: string;
user_id: string; user: string;
num_adventures?: number | null; num_adventures?: number | null;
}; };
@ -279,13 +279,13 @@ export type Attachment = {
file: string; file: string;
adventure: string; adventure: string;
extension: string; extension: string;
user_id: string; user: string;
name: string; name: string;
}; };
export type Lodging = { export type Lodging = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
type: string; type: string;
description: string | null; description: string | null;

View file

@ -150,7 +150,7 @@
{/if} {/if}
{#if adventure} {#if adventure}
{#if data.user && data.user.uuid == adventure.user_id} {#if data.user && data.user.uuid == adventure.user}
<div class="fixed bottom-6 right-6 z-50"> <div class="fixed bottom-6 right-6 z-50">
<button <button
class="btn btn-primary btn-circle w-16 h-16 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-110" class="btn btn-primary btn-circle w-16 h-16 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-110"

View file

@ -498,7 +498,7 @@
function recomendationToAdventure(recomendation: any) { function recomendationToAdventure(recomendation: any) {
adventureToEdit = { adventureToEdit = {
id: '', id: '',
user_id: null, user: null,
name: recomendation.name, name: recomendation.name,
latitude: recomendation.latitude, latitude: recomendation.latitude,
longitude: recomendation.longitude, longitude: recomendation.longitude,
@ -513,7 +513,7 @@
icon: osmTagToEmoji(recomendation.tag), icon: osmTagToEmoji(recomendation.tag),
id: '', id: '',
name: recomendation.tag, name: recomendation.tag,
user_id: '' user: ''
}, },
attachments: [] attachments: []
}; };
@ -748,7 +748,7 @@
</div> </div>
{/if} {/if}
{#if collection && collection.id} {#if collection && collection.id}
{#if data.user && data.user.uuid && (data.user.uuid == collection.user_id || (collection.shared_with && collection.shared_with.includes(data.user.uuid))) && !collection.is_archived} {#if data.user && data.user.uuid && (data.user.uuid == collection.user || (collection.shared_with && collection.shared_with.includes(data.user.uuid))) && !collection.is_archived}
<div class="fixed bottom-4 right-4 z-[999]"> <div class="fixed bottom-4 right-4 z-[999]">
<div class="flex flex-row items-center justify-center gap-4"> <div class="flex flex-row items-center justify-center gap-4">
<div class="dropdown dropdown-top dropdown-end z-[999]"> <div class="dropdown dropdown-top dropdown-end z-[999]">
@ -760,7 +760,7 @@
tabindex="0" tabindex="0"
class="dropdown-content z-[1] menu p-4 shadow bg-base-300 text-base-content rounded-box w-52 gap-4" class="dropdown-content z-[1] menu p-4 shadow bg-base-300 text-base-content rounded-box w-52 gap-4"
> >
{#if collection.user_id === data.user.uuid} {#if collection.user === data.user.uuid}
<p class="text-center font-bold text-lg">{$t('adventures.link_new')}</p> <p class="text-center font-bold text-lg">{$t('adventures.link_new')}</p>
<button <button
class="btn btn-primary" class="btn btn-primary"