mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-23 23:09:37 +02:00
Rename Adventures to Locations (#696)
* 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. * Refactor adventure-related views and components to use "Location" terminology - Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer. - Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer. - Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion. - Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities. - Updated ReverseGeocodeViewSet to utilize LocationSerializer. - Added ActivityTypesView to retrieve distinct activity types from locations. - Refactored user views to replace AdventureSerializer with LocationSerializer. - Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others. - Adjusted API endpoints in frontend routes to align with new location-based structure. - Ensured all references to adventures are replaced with locations across the codebase. * refactor: rename adventures to locations across the application - Updated localization files to replace adventure-related terms with location-related terms. - Refactored TypeScript types and variables from Adventure to Location in various routes and components. - Adjusted UI elements and labels to reflect the change from adventures to locations. - Ensured all references to adventures in the codebase are consistent with the new location terminology. * Refactor code structure for improved readability and maintainability * feat: Implement location details page with server-side loading and deletion functionality - Added +page.server.ts to handle server-side loading of additional location info. - Created +page.svelte for displaying location details, including images, visits, and maps. - Integrated GPX file handling and rendering on the map. - Updated map route to link to locations instead of adventures. - Refactored profile and search routes to use LocationCard instead of AdventureCard. * docs: Update terminology from "Adventure" to "Location" and enhance project overview * docs: Clarify collection examples in usage documentation * feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting * Refactor adventure references to locations across the backend and frontend - Updated CategoryViewSet to reflect location context instead of adventures. - Modified ChecklistViewSet to include locations in retrieval logic. - Changed GlobalSearchView to search for locations instead of adventures. - Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures. - Refactored LocationImageViewSet to remove unused import. - Updated LocationViewSet to clarify public access for locations. - Changed LodgingViewSet to reference locations instead of adventures. - Modified NoteViewSet to prevent listing all locations. - Updated RecommendationsViewSet to handle locations in parsing and response. - Adjusted ReverseGeocodeViewSet to search through user locations. - Updated StatsViewSet to count locations instead of adventures. - Changed TagsView to reflect activity types for locations. - Updated TransportationViewSet to reference locations instead of adventures. - Added new translations for search results related to locations in multiple languages. - Updated dashboard and profile pages to reflect location counts instead of adventure counts. - Adjusted search routes to handle locations instead of adventures. * Update banner image * style: Update stats component background and border for improved visibility * refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
This commit is contained in:
parent
5308ec21d6
commit
493a13995c
115 changed files with 3148 additions and 2759 deletions
|
@ -1,10 +1,9 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
import os
|
||||
from typing import Iterable
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from adventures.managers import AdventureManager
|
||||
from adventures.managers import LocationManager
|
||||
import threading
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
|
@ -13,46 +12,45 @@ from django_resized import ResizedImageField
|
|||
from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
from adventures.utils.timezones import TIMEZONES
|
||||
|
||||
def background_geocode_and_assign(adventure_id: str):
|
||||
print(f"[Adventure Geocode Thread] Starting geocode for adventure {adventure_id}")
|
||||
def background_geocode_and_assign(location_id: str):
|
||||
print(f"[Location Geocode Thread] Starting geocode for location {location_id}")
|
||||
try:
|
||||
adventure = Adventure.objects.get(id=adventure_id)
|
||||
if not (adventure.latitude and adventure.longitude):
|
||||
location = Location.objects.get(id=location_id)
|
||||
if not (location.latitude and location.longitude):
|
||||
return
|
||||
|
||||
from adventures.geocoding import reverse_geocode # or wherever you defined it
|
||||
is_visited = adventure.is_visited_status()
|
||||
result = reverse_geocode(adventure.latitude, adventure.longitude, adventure.user_id)
|
||||
is_visited = location.is_visited_status()
|
||||
result = reverse_geocode(location.latitude, location.longitude, location.user)
|
||||
|
||||
if 'region_id' in result:
|
||||
region = Region.objects.filter(id=result['region_id']).first()
|
||||
if region:
|
||||
adventure.region = region
|
||||
location.region = region
|
||||
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:
|
||||
city = City.objects.filter(id=result['city_id']).first()
|
||||
if city:
|
||||
adventure.city = city
|
||||
location.city = city
|
||||
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:
|
||||
country = Country.objects.filter(country_code=result['country_id']).first()
|
||||
if country:
|
||||
adventure.country = country
|
||||
location.country = country
|
||||
|
||||
# Save updated location info
|
||||
# Save updated location info, skip geocode threading
|
||||
adventure.save(update_fields=["region", "city", "country"], _skip_geocode=True)
|
||||
|
||||
# print(f"[Adventure Geocode Thread] Successfully processed {adventure_id}: {adventure.name} - {adventure.latitude}, {adventure.longitude}")
|
||||
location.save(update_fields=["region", "city", "country"], _skip_geocode=True)
|
||||
|
||||
except Exception as e:
|
||||
# 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):
|
||||
import os
|
||||
|
@ -62,6 +60,7 @@ def validate_file_extension(value):
|
|||
if not ext.lower() in valid_extensions:
|
||||
raise ValidationError('Unsupported file extension.')
|
||||
|
||||
# Legacy support for old adventure types, not used in newer versions since custom categories are now used
|
||||
ADVENTURE_TYPES = [
|
||||
('general', 'General 🌍'),
|
||||
('outdoor', 'Outdoor 🏞️'),
|
||||
|
@ -87,426 +86,6 @@ ADVENTURE_TYPES = [
|
|||
('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 = [
|
||||
('hotel', 'Hotel'),
|
||||
('hostel', 'Hostel'),
|
||||
|
@ -533,13 +112,13 @@ TRANSPORTATION_TYPES = [
|
|||
]
|
||||
|
||||
# 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()
|
||||
|
||||
class Visit(models.Model):
|
||||
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)
|
||||
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)
|
||||
|
@ -552,18 +131,18 @@ class Visit(models.Model):
|
|||
raise ValidationError('The start date must be before or equal to the end date.')
|
||||
|
||||
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.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)
|
||||
|
||||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True)
|
||||
name = models.CharField(max_length=200)
|
||||
location = models.CharField(max_length=200, blank=True, null=True)
|
||||
activity_types = ArrayField(models.CharField(
|
||||
tags = ArrayField(models.CharField(
|
||||
max_length=100), blank=True, null=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
rating = models.FloatField(blank=True, null=True)
|
||||
|
@ -578,12 +157,12 @@ class Adventure(models.Model):
|
|||
country = models.ForeignKey(Country, on_delete=models.SET_NULL, blank=True, null=True)
|
||||
|
||||
# Changed from ForeignKey to ManyToManyField
|
||||
collections = models.ManyToManyField('Collection', blank=True, related_name='adventures')
|
||||
collections = models.ManyToManyField('Collection', blank=True, related_name='locations')
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
objects = AdventureManager()
|
||||
objects = LocationManager()
|
||||
|
||||
def is_visited_status(self):
|
||||
current_date = timezone.now().date()
|
||||
|
@ -609,18 +188,18 @@ class Adventure(models.Model):
|
|||
if self.pk: # Only check if the instance has been saved
|
||||
for collection in self.collections.all():
|
||||
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
|
||||
if self.user_id != collection.user_id:
|
||||
if self.user != collection.user:
|
||||
# Check if this is a shared collection scenario
|
||||
# Allow if the adventure owner has access to the collection through sharing
|
||||
if not collection.shared_with.filter(uuid=self.user_id.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}')
|
||||
# Allow if the location owner has access to the collection through sharing
|
||||
if not collection.shared_with.filter(uuid=self.user.uuid).exists():
|
||||
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.user_id != self.category.user_id:
|
||||
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}')
|
||||
if self.user != self.category.user:
|
||||
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):
|
||||
if force_insert and force_update:
|
||||
|
@ -628,7 +207,7 @@ class Adventure(models.Model):
|
|||
|
||||
if not self.category:
|
||||
category, _ = Category.objects.get_or_create(
|
||||
user_id=self.user_id,
|
||||
user=self.user,
|
||||
name='general',
|
||||
defaults={'display_name': 'General', 'icon': '🌍'}
|
||||
)
|
||||
|
@ -671,8 +250,8 @@ class Adventure(models.Model):
|
|||
class Collection(models.Model):
|
||||
#id = models.AutoField(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)
|
||||
name = models.CharField(max_length=200)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
is_public = models.BooleanField(default=False)
|
||||
|
@ -684,13 +263,13 @@ class Collection(models.Model):
|
|||
shared_with = models.ManyToManyField(User, related_name='shared_with', blank=True)
|
||||
link = models.URLField(blank=True, null=True, max_length=2083)
|
||||
|
||||
# if connected adventures are private and collection is public, raise an error
|
||||
# if connected locations are private and collection is public, raise an error
|
||||
def clean(self):
|
||||
if self.is_public and self.pk: # Only check if the instance has a primary key
|
||||
# Updated to use the new related_name 'adventures'
|
||||
for adventure in self.adventures.all():
|
||||
if not adventure.is_public:
|
||||
raise ValidationError(f'Public collections cannot be associated with private adventures. Collection: {self.name} Adventure: {adventure.name}')
|
||||
# Updated to use the new related_name 'locations'
|
||||
for location in self.locations.all():
|
||||
if not location.is_public:
|
||||
raise ValidationError(f'Public collections cannot be associated with private locations. Collection: {self.name} Location: {location.name}')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -698,8 +277,8 @@ class Collection(models.Model):
|
|||
class Transportation(models.Model):
|
||||
#id = models.AutoField(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)
|
||||
type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES)
|
||||
name = models.CharField(max_length=200)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
@ -729,8 +308,8 @@ class Transportation(models.Model):
|
|||
if self.collection:
|
||||
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)
|
||||
if self.user_id != self.collection.user_id:
|
||||
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)
|
||||
if self.user != self.collection.user:
|
||||
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):
|
||||
return self.name
|
||||
|
@ -738,8 +317,8 @@ class Transportation(models.Model):
|
|||
class Note(models.Model):
|
||||
#id = models.AutoField(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)
|
||||
name = models.CharField(max_length=200)
|
||||
content = models.TextField(blank=True, null=True)
|
||||
links = ArrayField(models.URLField(), blank=True, null=True)
|
||||
|
@ -753,8 +332,8 @@ class Note(models.Model):
|
|||
if self.collection:
|
||||
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)
|
||||
if self.user_id != self.collection.user_id:
|
||||
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)
|
||||
if self.user != self.collection.user:
|
||||
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):
|
||||
return self.name
|
||||
|
@ -762,8 +341,8 @@ class Note(models.Model):
|
|||
class Checklist(models.Model):
|
||||
# id = models.AutoField(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)
|
||||
name = models.CharField(max_length=200)
|
||||
date = models.DateField(blank=True, null=True)
|
||||
is_public = models.BooleanField(default=False)
|
||||
|
@ -775,8 +354,8 @@ class Checklist(models.Model):
|
|||
if self.collection:
|
||||
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)
|
||||
if self.user_id != self.collection.user_id:
|
||||
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)
|
||||
if self.user != self.collection.user:
|
||||
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):
|
||||
return self.name
|
||||
|
@ -784,8 +363,8 @@ class Checklist(models.Model):
|
|||
class ChecklistItem(models.Model):
|
||||
#id = models.AutoField(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)
|
||||
name = models.CharField(max_length=200)
|
||||
is_checked = models.BooleanField(default=False)
|
||||
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE)
|
||||
|
@ -795,8 +374,8 @@ class ChecklistItem(models.Model):
|
|||
def clean(self):
|
||||
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)
|
||||
if self.user_id != self.checklist.user_id:
|
||||
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)
|
||||
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.username + ' Checklist item owner: ' + self.user.username)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -812,9 +391,9 @@ class PathAndRename:
|
|||
filename = f"{uuid.uuid4()}.{ext}"
|
||||
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)
|
||||
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(
|
||||
force_format="WEBP",
|
||||
quality=75,
|
||||
|
@ -823,7 +402,7 @@ class AdventureImage(models.Model):
|
|||
null=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)
|
||||
|
||||
def clean(self):
|
||||
|
@ -858,10 +437,10 @@ class AdventureImage(models.Model):
|
|||
|
||||
class Attachment(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
user_id = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, default=default_user_id)
|
||||
user = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, default=default_user)
|
||||
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)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
|
@ -874,15 +453,15 @@ class Attachment(models.Model):
|
|||
|
||||
class Category(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
user_id = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, default=default_user_id)
|
||||
user = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, default=default_user)
|
||||
name = models.CharField(max_length=200)
|
||||
display_name = models.CharField(max_length=200)
|
||||
icon = models.CharField(max_length=200, default='🌍')
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'Categories'
|
||||
unique_together = ['name', 'user_id']
|
||||
unique_together = ['name', 'user']
|
||||
|
||||
def clean(self) -> None:
|
||||
self.name = self.name.lower().strip()
|
||||
|
@ -895,8 +474,8 @@ class Category(models.Model):
|
|||
|
||||
class Lodging(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
user_id = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, default=default_user_id)
|
||||
user = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, default=default_user)
|
||||
name = models.CharField(max_length=200)
|
||||
type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other')
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
@ -921,9 +500,9 @@ class Lodging(models.Model):
|
|||
|
||||
if self.collection:
|
||||
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)
|
||||
if self.user_id != self.collection.user_id:
|
||||
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 associated with a public collection must be public. Collection: ' + self.collection.name + ' Lodging: ' + self.name)
|
||||
if self.user != self.collection.user:
|
||||
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):
|
||||
return self.name
|
Loading…
Add table
Add a link
Reference in a new issue