mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-05 05:05:17 +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:
parent
7a17e0e1d8
commit
241d27a1a6
50 changed files with 918 additions and 792 deletions
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
from django.contrib import admin
|
||||
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 allauth.account.decorators import secure_admin_login
|
||||
|
||||
|
@ -22,8 +22,8 @@ def trigger_geocoding(modeladmin, request, queryset):
|
|||
|
||||
|
||||
class AdventureAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'get_category', 'get_visit_count', 'user_id', 'is_public')
|
||||
list_filter = ( 'user_id', 'is_public')
|
||||
list_display = ('name', 'get_category', 'get_visit_count', 'user', 'is_public')
|
||||
list_filter = ( 'user', 'is_public')
|
||||
search_fields = ('name',)
|
||||
readonly_fields = ('city', 'region', 'country')
|
||||
actions = [trigger_geocoding]
|
||||
|
@ -97,7 +97,7 @@ class CustomUserAdmin(UserAdmin):
|
|||
return
|
||||
|
||||
class AdventureImageAdmin(admin.ModelAdmin):
|
||||
list_display = ('user_id', 'image_display')
|
||||
list_display = ('user', 'image_display')
|
||||
|
||||
def image_display(self, obj):
|
||||
if obj.image:
|
||||
|
@ -109,7 +109,7 @@ class AdventureImageAdmin(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')
|
||||
search_fields = ('notes',)
|
||||
|
||||
|
@ -125,19 +125,19 @@ class VisitAdmin(admin.ModelAdmin):
|
|||
image_display.short_description = 'Image Preview'
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'user_id', 'display_name', 'icon')
|
||||
list_display = ('name', 'user', 'display_name', 'icon')
|
||||
search_fields = ('name', 'display_name')
|
||||
|
||||
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(Adventure, AdventureAdmin)
|
||||
admin.site.register(Location, AdventureAdmin)
|
||||
admin.site.register(Collection, CollectionAdmin)
|
||||
admin.site.register(Visit, VisitAdmin)
|
||||
admin.site.register(Country, CountryAdmin)
|
||||
|
@ -147,7 +147,7 @@ admin.site.register(Transportation)
|
|||
admin.site.register(Note)
|
||||
admin.site.register(Checklist)
|
||||
admin.site.register(ChecklistItem)
|
||||
admin.site.register(AdventureImage, AdventureImageAdmin)
|
||||
admin.site.register(LocationImage, AdventureImageAdmin)
|
||||
admin.site.register(Category, CategoryAdmin)
|
||||
admin.site.register(City, CityAdmin)
|
||||
admin.site.register(VisitedCity)
|
||||
|
|
|
@ -167,7 +167,7 @@ def extractIsoCode(user, data):
|
|||
return {"error": "No region found"}
|
||||
|
||||
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
|
||||
city_visited = False
|
||||
|
@ -177,7 +177,7 @@ def extractIsoCode(user, data):
|
|||
if town_city_or_county:
|
||||
display_name = f"{town_city_or_county}, {region.name}, {country_code}"
|
||||
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:
|
||||
region_visited = True
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from adventures.models import Adventure
|
||||
from adventures.models import Location
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -38,8 +38,8 @@ class Command(BaseCommand):
|
|||
]
|
||||
|
||||
for name, location, type_ in adventures:
|
||||
Adventure.objects.create(
|
||||
user_id=user,
|
||||
Location.objects.create(
|
||||
user=user,
|
||||
name=name,
|
||||
location=location,
|
||||
type=type_,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from django.db import models
|
||||
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):
|
||||
query = Q()
|
||||
|
||||
if include_owned:
|
||||
query |= Q(user_id=user)
|
||||
query |= Q(user=user)
|
||||
|
||||
if include_shared:
|
||||
query |= Q(collections__shared_with=user)
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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,47 @@ 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)
|
||||
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}")
|
||||
|
||||
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 +62,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 +88,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 +114,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,13 +133,13 @@ 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)
|
||||
|
@ -583,7 +164,7 @@ class Adventure(models.Model):
|
|||
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 +190,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 +209,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': '🌍'}
|
||||
)
|
||||
|
@ -662,8 +243,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)
|
||||
|
@ -681,7 +262,7 @@ class Collection(models.Model):
|
|||
# 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}')
|
||||
raise ValidationError(f'Public collections cannot be associated with private locations. Collection: {self.name} Adventure: {adventure.name}')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -689,8 +270,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)
|
||||
|
@ -720,8 +301,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
|
||||
|
@ -729,8 +310,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)
|
||||
|
@ -744,8 +325,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
|
||||
|
@ -753,8 +334,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)
|
||||
|
@ -766,8 +347,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
|
||||
|
@ -775,8 +356,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)
|
||||
|
@ -786,8 +367,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
|
||||
|
@ -803,9 +384,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,
|
||||
|
@ -814,7 +395,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):
|
||||
|
@ -843,10 +424,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 __str__(self):
|
||||
|
@ -854,15 +435,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()
|
||||
|
@ -875,8 +456,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)
|
||||
|
@ -901,9 +482,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
|
|
@ -7,8 +7,8 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
|
|||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
# obj.user_id is FK to User, compare with request.user
|
||||
return obj.user_id == request.user
|
||||
# obj.user is FK to User, compare with request.user
|
||||
return obj.user == request.user
|
||||
|
||||
|
||||
class IsPublicReadOnly(permissions.BasePermission):
|
||||
|
@ -17,8 +17,8 @@ class IsPublicReadOnly(permissions.BasePermission):
|
|||
"""
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return obj.is_public or obj.user_id == request.user
|
||||
return obj.user_id == request.user
|
||||
return obj.is_public or obj.user == request.user
|
||||
return obj.user == request.user
|
||||
|
||||
|
||||
class CollectionShared(permissions.BasePermission):
|
||||
|
@ -48,10 +48,10 @@ class CollectionShared(permissions.BasePermission):
|
|||
|
||||
# Read permission if public or owner
|
||||
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
|
||||
if obj.user_id == user:
|
||||
if obj.user == user:
|
||||
return True
|
||||
|
||||
if hasattr(obj, 'collections'):
|
||||
|
@ -76,7 +76,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
|
|||
if request.method in permissions.SAFE_METHODS:
|
||||
if obj.is_public:
|
||||
return True
|
||||
if obj.user_id == user:
|
||||
if obj.user == user:
|
||||
return True
|
||||
# If user in shared_with of any collection related to obj
|
||||
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
|
||||
|
@ -88,7 +88,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
|
|||
return False
|
||||
|
||||
# For write methods, allow if owner or shared user
|
||||
if obj.user_id == user:
|
||||
if obj.user == user:
|
||||
return True
|
||||
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
|
||||
return True
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.utils import timezone
|
||||
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 main.utils import CustomModelSerializer
|
||||
from users.serializers import CustomUserDetailsSerializer
|
||||
|
@ -11,15 +11,15 @@ from integrations.models import ImmichIntegration
|
|||
|
||||
class AdventureImageSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = AdventureImage
|
||||
fields = ['id', 'image', 'adventure', 'is_primary', 'user_id', 'immich_id']
|
||||
read_only_fields = ['id', 'user_id']
|
||||
model = LocationImage
|
||||
fields = ['id', 'image', 'location', 'is_primary', 'user', 'immich_id']
|
||||
read_only_fields = ['id', 'user']
|
||||
|
||||
def to_representation(self, instance):
|
||||
# If immich_id is set, check for user integration once
|
||||
integration = None
|
||||
if instance.immich_id:
|
||||
integration = ImmichIntegration.objects.filter(user=instance.user_id).first()
|
||||
integration = ImmichIntegration.objects.filter(user=instance.user).first()
|
||||
if not integration:
|
||||
return None # Skip if Immich image but no integration
|
||||
|
||||
|
@ -42,8 +42,8 @@ class AttachmentSerializer(CustomModelSerializer):
|
|||
extension = serializers.SerializerMethodField()
|
||||
class Meta:
|
||||
model = Attachment
|
||||
fields = ['id', 'file', 'adventure', 'extension', 'name', 'user_id']
|
||||
read_only_fields = ['id', 'user_id']
|
||||
fields = ['id', 'file', 'location', 'extension', 'name', 'user']
|
||||
read_only_fields = ['id', 'user']
|
||||
|
||||
def get_extension(self, obj):
|
||||
return obj.file.name.split('.')[-1]
|
||||
|
@ -71,7 +71,7 @@ class CategorySerializer(serializers.ModelSerializer):
|
|||
def create(self, validated_data):
|
||||
user = self.context['request'].user
|
||||
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):
|
||||
for attr, value in validated_data.items():
|
||||
|
@ -82,7 +82,7 @@ class CategorySerializer(serializers.ModelSerializer):
|
|||
return instance
|
||||
|
||||
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):
|
||||
|
||||
|
@ -108,13 +108,13 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
model = Adventure
|
||||
model = Location
|
||||
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',
|
||||
'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):
|
||||
serializer = AdventureImageSerializer(obj.images.all(), many=True, context=self.context)
|
||||
|
@ -128,7 +128,7 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
|
||||
user = self.context['request'].user
|
||||
for collection in collections:
|
||||
if collection.user_id != user:
|
||||
if collection.user != user:
|
||||
raise serializers.ValidationError(
|
||||
f"Collection '{collection.name}' does not belong to the current user."
|
||||
)
|
||||
|
@ -140,7 +140,7 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
if category_data:
|
||||
user = self.context['request'].user
|
||||
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:
|
||||
return existing_category
|
||||
category_data['name'] = name
|
||||
|
@ -162,7 +162,7 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
icon = category_data.icon
|
||||
|
||||
category, created = Category.objects.get_or_create(
|
||||
user_id=user,
|
||||
user=user,
|
||||
name=name,
|
||||
defaults={
|
||||
'display_name': display_name,
|
||||
|
@ -172,7 +172,7 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
return category
|
||||
|
||||
def get_user(self, obj):
|
||||
user = obj.user_id
|
||||
user = obj.user
|
||||
return CustomUserDetailsSerializer(user).data
|
||||
|
||||
def get_is_visited(self, obj):
|
||||
|
@ -184,11 +184,11 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
collections_data = validated_data.pop('collections', [])
|
||||
|
||||
print(category_data)
|
||||
adventure = Adventure.objects.create(**validated_data)
|
||||
adventure = Location.objects.create(**validated_data)
|
||||
|
||||
# Handle visits
|
||||
for visit_data in visits_data:
|
||||
Visit.objects.create(adventure=adventure, **visit_data)
|
||||
Visit.objects.create(location=adventure, **visit_data)
|
||||
|
||||
# Handle category
|
||||
if category_data:
|
||||
|
@ -216,7 +216,7 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
|
||||
# Handle category - ONLY allow the adventure owner to change categories
|
||||
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
|
||||
category = self.get_or_create_category(category_data)
|
||||
instance.category = category
|
||||
|
@ -241,7 +241,7 @@ class AdventureSerializer(CustomModelSerializer):
|
|||
visit.save()
|
||||
updated_visit_ids.add(visit_id)
|
||||
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)
|
||||
|
||||
visits_to_delete = current_visit_ids - updated_visit_ids
|
||||
|
@ -258,13 +258,13 @@ class TransportationSerializer(CustomModelSerializer):
|
|||
class Meta:
|
||||
model = Transportation
|
||||
fields = [
|
||||
'id', 'user_id', 'type', 'name', 'description', 'rating',
|
||||
'id', 'user', 'type', 'name', 'description', 'rating',
|
||||
'link', 'date', 'flight_number', 'from_location', 'to_location',
|
||||
'is_public', 'collection', 'created_at', 'updated_at', 'end_date',
|
||||
'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):
|
||||
if (
|
||||
|
@ -284,29 +284,29 @@ class LodgingSerializer(CustomModelSerializer):
|
|||
class Meta:
|
||||
model = Lodging
|
||||
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',
|
||||
'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 Meta:
|
||||
model = Note
|
||||
fields = [
|
||||
'id', 'user_id', 'name', 'content', 'date', 'links',
|
||||
'id', 'user', 'name', 'content', 'date', 'links',
|
||||
'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 Meta:
|
||||
model = ChecklistItem
|
||||
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):
|
||||
items = ChecklistItemSerializer(many=True, source='checklistitem_set')
|
||||
|
@ -314,21 +314,21 @@ class ChecklistSerializer(CustomModelSerializer):
|
|||
class Meta:
|
||||
model = Checklist
|
||||
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):
|
||||
items_data = validated_data.pop('checklistitem_set')
|
||||
checklist = Checklist.objects.create(**validated_data)
|
||||
|
||||
for item_data in items_data:
|
||||
# Remove user_id from item_data to avoid constraint issues
|
||||
item_data.pop('user_id', None)
|
||||
# Set user_id from the parent checklist
|
||||
# Remove user from item_data to avoid constraint issues
|
||||
item_data.pop('user', None)
|
||||
# Set user from the parent checklist
|
||||
ChecklistItem.objects.create(
|
||||
checklist=checklist,
|
||||
user_id=checklist.user_id,
|
||||
user=checklist.user,
|
||||
**item_data
|
||||
)
|
||||
return checklist
|
||||
|
@ -348,8 +348,8 @@ class ChecklistSerializer(CustomModelSerializer):
|
|||
# Update or create items
|
||||
updated_item_ids = set()
|
||||
for item_data in items_data:
|
||||
# Remove user_id from item_data to avoid constraint issues
|
||||
item_data.pop('user_id', None)
|
||||
# Remove user from item_data to avoid constraint issues
|
||||
item_data.pop('user', None)
|
||||
|
||||
item_id = item_data.get('id')
|
||||
if item_id:
|
||||
|
@ -363,14 +363,14 @@ class ChecklistSerializer(CustomModelSerializer):
|
|||
# If ID is provided but doesn't exist, create new item
|
||||
ChecklistItem.objects.create(
|
||||
checklist=instance,
|
||||
user_id=instance.user_id,
|
||||
user=instance.user,
|
||||
**item_data
|
||||
)
|
||||
else:
|
||||
# If no ID is provided, create new item
|
||||
ChecklistItem.objects.create(
|
||||
checklist=instance,
|
||||
user_id=instance.user_id,
|
||||
user=instance.user,
|
||||
**item_data
|
||||
)
|
||||
|
||||
|
@ -399,8 +399,8 @@ class CollectionSerializer(CustomModelSerializer):
|
|||
|
||||
class Meta:
|
||||
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']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
||||
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']
|
||||
|
||||
def to_representation(self, instance):
|
||||
representation = super().to_representation(instance)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django.db.models.signals import m2m_changed
|
||||
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):
|
||||
"""
|
||||
Signal handler to update adventure publicity when collections are added/removed
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from adventures.models import AdventureImage, Attachment
|
||||
from adventures.models import LocationImage, Attachment
|
||||
|
||||
protected_paths = ['images/', 'attachments/']
|
||||
|
||||
|
@ -10,10 +10,10 @@ def checkFilePermission(fileId, user, mediaType):
|
|||
# Construct the full relative path to match the database field
|
||||
image_path = f"images/{fileId}"
|
||||
# Fetch the AdventureImage object
|
||||
adventure = AdventureImage.objects.get(image=image_path).adventure
|
||||
adventure = LocationImage.objects.get(image=image_path).location
|
||||
if adventure.is_public:
|
||||
return True
|
||||
elif adventure.user_id == user:
|
||||
elif adventure.user == user:
|
||||
return True
|
||||
elif adventure.collections.exists():
|
||||
# Check if the user is in any collection's shared_with list
|
||||
|
@ -23,7 +23,7 @@ def checkFilePermission(fileId, user, mediaType):
|
|||
return False
|
||||
else:
|
||||
return False
|
||||
except AdventureImage.DoesNotExist:
|
||||
except LocationImage.DoesNotExist:
|
||||
return False
|
||||
elif mediaType == 'attachments/':
|
||||
try:
|
||||
|
@ -31,10 +31,10 @@ def checkFilePermission(fileId, user, mediaType):
|
|||
attachment_path = f"attachments/{fileId}"
|
||||
# Fetch the Attachment object
|
||||
attachment = Attachment.objects.get(file=attachment_path)
|
||||
adventure = attachment.adventure
|
||||
adventure = attachment.location
|
||||
if adventure.is_public:
|
||||
return True
|
||||
elif adventure.user_id == user:
|
||||
elif adventure.user == user:
|
||||
return True
|
||||
elif adventure.collections.exists():
|
||||
# Check if the user is in any collection's shared_with list
|
||||
|
|
419
backend/server/adventures/utils/timezones.py
Normal file
419
backend/server/adventures/utils/timezones.py
Normal 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"
|
||||
]
|
|
@ -2,7 +2,7 @@ from rest_framework import viewsets
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from adventures.models import Adventure
|
||||
from adventures.models import Location
|
||||
|
||||
class ActivityTypesView(viewsets.ViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
@ -18,7 +18,7 @@ class ActivityTypesView(viewsets.ViewSet):
|
|||
Returns:
|
||||
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 = []
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework.permissions import IsAuthenticated
|
|||
from rest_framework.response import Response
|
||||
from django.db.models import Q
|
||||
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 integrations.models import ImmichIntegration
|
||||
import uuid
|
||||
|
@ -26,7 +26,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
|
||||
instance = self.get_object()
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
instance.is_primary = True
|
||||
|
@ -46,11 +46,11 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
adventure_id = request.data.get('adventure')
|
||||
try:
|
||||
adventure = Adventure.objects.get(id=adventure_id)
|
||||
except Adventure.DoesNotExist:
|
||||
adventure = Location.objects.get(id=adventure_id)
|
||||
except Location.DoesNotExist:
|
||||
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
|
||||
if adventure.collections.exists():
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
request.data.get('immich_id')):
|
||||
|
||||
|
@ -74,7 +74,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
|
||||
# Get the shared user's Immich integration
|
||||
try:
|
||||
user_integration = ImmichIntegration.objects.get(user_id=request.user)
|
||||
user_integration = ImmichIntegration.objects.get(user=request.user)
|
||||
except ImmichIntegration.DoesNotExist:
|
||||
return Response({
|
||||
"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
|
||||
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)
|
||||
|
||||
|
@ -145,11 +145,11 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
|
||||
adventure_id = request.data.get('adventure')
|
||||
try:
|
||||
adventure = Adventure.objects.get(id=adventure_id)
|
||||
except Adventure.DoesNotExist:
|
||||
adventure = Location.objects.get(id=adventure_id)
|
||||
except Location.DoesNotExist:
|
||||
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 super().update(request, *args, **kwargs)
|
||||
|
@ -165,7 +165,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
|
||||
instance = self.get_object()
|
||||
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 super().destroy(request, *args, **kwargs)
|
||||
|
@ -176,7 +176,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
|
||||
instance = self.get_object()
|
||||
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 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)
|
||||
|
||||
# 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__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
|
||||
)
|
||||
).distinct()
|
||||
|
@ -204,12 +204,12 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
# Updated to include images from adventures the user owns OR has shared access to
|
||||
return AdventureImage.objects.filter(
|
||||
Q(adventure__user_id=self.request.user) | # User owns the adventure
|
||||
return LocationImage.objects.filter(
|
||||
Q(adventure__user=self.request.user) | # User owns the adventure
|
||||
Q(adventure__collections__shared_with=self.request.user) # User has shared access via collection
|
||||
).distinct()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
# Always set the image owner to the adventure owner, not the current user
|
||||
adventure = serializer.validated_data['adventure']
|
||||
serializer.save(user_id=adventure.user_id)
|
||||
serializer.save(user=adventure.user)
|
|
@ -8,7 +8,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.response import Response
|
||||
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.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
|
||||
from adventures.utils import pagination
|
||||
|
@ -35,13 +35,13 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
|
||||
if not user.is_authenticated:
|
||||
if self.action in public_allowed_actions:
|
||||
return Adventure.objects.retrieve_adventures(
|
||||
return Location.objects.retrieve_adventures(
|
||||
user, include_public=True
|
||||
).order_by('-updated_at')
|
||||
return Adventure.objects.none()
|
||||
return Location.objects.none()
|
||||
|
||||
include_public = self.action in public_allowed_actions
|
||||
return Adventure.objects.retrieve_adventures(
|
||||
return Location.objects.retrieve_adventures(
|
||||
user,
|
||||
include_public=include_public,
|
||||
include_owned=True,
|
||||
|
@ -116,7 +116,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
# Use the current user as owner since ManyToMany allows multiple collection owners
|
||||
user_to_assign = self.request.user
|
||||
|
||||
serializer.save(user_id=user_to_assign)
|
||||
serializer.save(user=user_to_assign)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""Update adventure."""
|
||||
|
@ -153,12 +153,12 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
# Handle 'all' types
|
||||
if 'all' in types:
|
||||
types = Category.objects.filter(
|
||||
user_id=request.user
|
||||
user=request.user
|
||||
).values_list('name', flat=True)
|
||||
else:
|
||||
# Validate provided types
|
||||
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
|
||||
):
|
||||
return Response(
|
||||
|
@ -167,9 +167,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
)
|
||||
|
||||
# Build base queryset
|
||||
queryset = Adventure.objects.filter(
|
||||
category__in=Category.objects.filter(name__in=types, user_id=request.user),
|
||||
user_id=request.user.id
|
||||
queryset = Location.objects.filter(
|
||||
category__in=Category.objects.filter(name__in=types, user=request.user),
|
||||
user=request.user.id
|
||||
)
|
||||
|
||||
# Apply visit status filtering
|
||||
|
@ -187,12 +187,12 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
include_collections = request.query_params.get('include_collections', 'false') == 'true'
|
||||
|
||||
# Build queryset with collection filtering
|
||||
base_filter = Q(user_id=request.user.id)
|
||||
base_filter = Q(user=request.user.id)
|
||||
|
||||
if include_collections:
|
||||
queryset = Adventure.objects.filter(base_filter)
|
||||
queryset = Location.objects.filter(base_filter)
|
||||
else:
|
||||
queryset = Adventure.objects.filter(base_filter, collections__isnull=True)
|
||||
queryset = Location.objects.filter(base_filter, collections__isnull=True)
|
||||
|
||||
queryset = self.apply_sorting(queryset)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
|
@ -225,7 +225,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
def _validate_collection_permissions(self, collections):
|
||||
"""Validate user has permission to use all provided collections. Only the owner or shared users can use 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()):
|
||||
raise PermissionDenied(
|
||||
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)."""
|
||||
# Check permissions for new collections being added
|
||||
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()):
|
||||
raise PermissionDenied(
|
||||
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
|
||||
|
||||
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()):
|
||||
raise PermissionDenied(
|
||||
f"You cannot remove the adventure from collection '{collection.name}' "
|
||||
|
@ -284,7 +284,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
return True
|
||||
|
||||
# Check ownership
|
||||
if user.is_authenticated and adventure.user_id == user:
|
||||
if user.is_authenticated and adventure.user == user:
|
||||
return True
|
||||
|
||||
# Check shared collection access
|
||||
|
|
|
@ -2,7 +2,7 @@ from rest_framework import viewsets, status
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from adventures.models import Adventure, Attachment
|
||||
from adventures.models import Location, Attachment
|
||||
from adventures.serializers import AttachmentSerializer
|
||||
|
||||
class AttachmentViewSet(viewsets.ModelViewSet):
|
||||
|
@ -10,7 +10,7 @@ class AttachmentViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = [IsAuthenticated]
|
||||
|
||||
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'])
|
||||
def attachment_delete(self, request, *args, **kwargs):
|
||||
|
@ -19,38 +19,38 @@ class AttachmentViewSet(viewsets.ModelViewSet):
|
|||
def create(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
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:
|
||||
adventure = Adventure.objects.get(id=adventure_id)
|
||||
except Adventure.DoesNotExist:
|
||||
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if adventure.user_id != request.user:
|
||||
# Check if the adventure has any collections
|
||||
if adventure.collections.exists():
|
||||
# Check if the user is in the shared_with list of any of the adventure's collections
|
||||
location = Location.objects.get(id=location_id)
|
||||
except Location.DoesNotExist:
|
||||
return Response({"error": "Location not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if location.user != request.user:
|
||||
# Check if the location has any collections
|
||||
if location.collections.exists():
|
||||
# Check if the user is in the shared_with list of any of the location's collections
|
||||
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():
|
||||
user_has_access = True
|
||||
break
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
adventure_id = self.request.data.get('adventure')
|
||||
adventure = Adventure.objects.get(id=adventure_id)
|
||||
|
||||
# If the adventure belongs to collections, set the owner to the collection owner
|
||||
if adventure.collections.exists():
|
||||
location_id = self.request.data.get('location')
|
||||
location = Location.objects.get(id=location_id)
|
||||
|
||||
# If the location belongs to collections, set the owner to the collection owner
|
||||
if location.collections.exists():
|
||||
# Get the first collection's owner (assuming all collections have the same owner)
|
||||
collection = adventure.collections.first()
|
||||
serializer.save(user_id=collection.user_id)
|
||||
collection = location.collections.first()
|
||||
serializer.save(user=collection.user)
|
||||
else:
|
||||
# Otherwise, set the owner to the request user
|
||||
serializer.save(user_id=self.request.user)
|
||||
serializer.save(user=self.request.user)
|
|
@ -2,7 +2,7 @@ from rest_framework import viewsets
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from adventures.models import Category, Adventure
|
||||
from adventures.models import Category, Location
|
||||
from adventures.serializers import CategorySerializer
|
||||
|
||||
class CategoryViewSet(viewsets.ModelViewSet):
|
||||
|
@ -11,7 +11,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = [IsAuthenticated]
|
||||
|
||||
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'])
|
||||
def categories(self, request):
|
||||
|
@ -24,7 +24,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
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
|
||||
=400)
|
||||
|
||||
|
@ -32,11 +32,11 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
|||
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
|
||||
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:
|
||||
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)
|
|
@ -24,7 +24,7 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
|||
if not request.user.is_authenticated:
|
||||
return Response({"error": "User is not authenticated"}, status=400)
|
||||
queryset = Checklist.objects.filter(
|
||||
Q(user_id=request.user.id)
|
||||
Q(user=request.user.id)
|
||||
)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
@ -41,12 +41,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
|||
if self.action == 'retrieve':
|
||||
# For individual adventure retrieval, include public adventures
|
||||
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')
|
||||
else:
|
||||
# For other actions, include user's own adventures and shared adventures
|
||||
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')
|
||||
|
||||
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:
|
||||
# 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.")
|
||||
elif new_collection is 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.")
|
||||
|
||||
# Perform the update
|
||||
|
@ -94,11 +94,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
|||
|
||||
if new_collection is not None and new_collection!=instance.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.")
|
||||
elif new_collection is 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.")
|
||||
|
||||
# Perform the update
|
||||
|
@ -119,12 +119,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
|||
if collection:
|
||||
user = self.request.user
|
||||
# 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
|
||||
raise PermissionDenied("You do not have permission to use this 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
|
||||
|
||||
# Save the adventure with the current user as the owner
|
||||
serializer.save(user_id=self.request.user)
|
||||
serializer.save(user=self.request.user)
|
|
@ -4,7 +4,7 @@ from django.db import transaction
|
|||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
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.serializers import CollectionSerializer
|
||||
from users.models import CustomUser as User
|
||||
|
@ -16,7 +16,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||
pagination_class = pagination.StandardResultsSetPagination
|
||||
|
||||
# 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):
|
||||
order_by = self.request.query_params.get('order_by', 'name')
|
||||
|
@ -55,7 +55,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||
# make sure the user is authenticated
|
||||
if not request.user.is_authenticated:
|
||||
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)
|
||||
collections = self.paginate_and_respond(queryset, request)
|
||||
return collections
|
||||
|
@ -66,7 +66,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||
return Response({"error": "User is not authenticated"}, status=400)
|
||||
|
||||
queryset = Collection.objects.filter(
|
||||
Q(user_id=request.user.id)
|
||||
Q(user=request.user.id)
|
||||
)
|
||||
|
||||
queryset = self.apply_sorting(queryset)
|
||||
|
@ -80,7 +80,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||
return Response({"error": "User is not authenticated"}, status=400)
|
||||
|
||||
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)
|
||||
|
@ -99,7 +99,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||
if 'collection' in serializer.validated_data:
|
||||
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 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)
|
||||
|
||||
# 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']
|
||||
|
||||
# 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:
|
||||
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user_id}")
|
||||
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}")
|
||||
return Response({"error": "User does not own the collection"}, status=400)
|
||||
|
||||
# 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 collection becomes public, make all adventures public
|
||||
|
@ -129,7 +129,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||
adventure_ids_to_set_private.append(adventure.id)
|
||||
|
||||
# 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
|
||||
# These still use direct ForeignKey relationships
|
||||
|
@ -207,28 +207,28 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
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']:
|
||||
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()
|
||||
|
||||
if self.action == 'retrieve':
|
||||
if not self.request.user.is_authenticated:
|
||||
return Collection.objects.filter(is_public=True)
|
||||
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()
|
||||
|
||||
# For list action, include collections owned by the user or shared with the user, that are not archived
|
||||
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()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
# 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):
|
||||
paginator = self.pagination_class()
|
||||
|
|
|
@ -3,7 +3,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.db.models import Q
|
||||
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 worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion
|
||||
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer
|
||||
|
@ -31,14 +31,14 @@ class GlobalSearchView(viewsets.ViewSet):
|
|||
}
|
||||
|
||||
# Adventures: Full-Text Search
|
||||
adventures = Adventure.objects.annotate(
|
||||
adventures = Location.objects.annotate(
|
||||
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
|
||||
|
||||
# Collections: Partial Match Search
|
||||
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
|
||||
|
||||
|
@ -64,10 +64,10 @@ class GlobalSearchView(viewsets.ViewSet):
|
|||
results["cities"] = CitySerializer(cities, many=True).data
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
return Response(results)
|
||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from icalendar import Calendar, Event, vText, vCalAddress
|
||||
from datetime import datetime, timedelta
|
||||
from adventures.models import Adventure
|
||||
from adventures.models import Location
|
||||
from adventures.serializers import AdventureSerializer
|
||||
|
||||
class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
|
||||
|
@ -12,11 +12,10 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
|
|||
|
||||
@action(detail=False, methods=['get'])
|
||||
def generate(self, request):
|
||||
adventures = Adventure.objects.filter(user_id=request.user)
|
||||
adventures = Location.objects.filter(user=request.user)
|
||||
serializer = AdventureSerializer(adventures, many=True)
|
||||
user = request.user
|
||||
name = f"{user.first_name} {user.last_name}"
|
||||
print(serializer.data)
|
||||
|
||||
cal = Calendar()
|
||||
cal.add('prodid', '-//My Adventure Calendar//example.com//')
|
||||
|
|
|
@ -17,7 +17,7 @@ class LodgingViewSet(viewsets.ModelViewSet):
|
|||
if not request.user.is_authenticated:
|
||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||
queryset = Lodging.objects.filter(
|
||||
Q(user_id=request.user.id)
|
||||
Q(user=request.user.id)
|
||||
)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
@ -27,11 +27,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
|
|||
if self.action == 'retrieve':
|
||||
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
|
||||
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')
|
||||
# For other actions, include user's own adventures and shared adventures
|
||||
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')
|
||||
|
||||
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:
|
||||
# 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.")
|
||||
elif new_collection is 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.")
|
||||
|
||||
# Perform the update
|
||||
|
@ -73,12 +73,12 @@ class LodgingViewSet(viewsets.ModelViewSet):
|
|||
if collection:
|
||||
user = self.request.user
|
||||
# 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
|
||||
raise PermissionDenied("You do not have permission to use this 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
|
||||
|
||||
# Save the adventure with the current user as the owner
|
||||
serializer.save(user_id=self.request.user)
|
||||
serializer.save(user=self.request.user)
|
|
@ -24,7 +24,7 @@ class NoteViewSet(viewsets.ModelViewSet):
|
|||
if not request.user.is_authenticated:
|
||||
return Response({"error": "User is not authenticated"}, status=400)
|
||||
queryset = Note.objects.filter(
|
||||
Q(user_id=request.user.id)
|
||||
Q(user=request.user.id)
|
||||
)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
@ -41,12 +41,12 @@ class NoteViewSet(viewsets.ModelViewSet):
|
|||
if self.action == 'retrieve':
|
||||
# For individual adventure retrieval, include public adventures
|
||||
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')
|
||||
else:
|
||||
# For other actions, include user's own adventures and shared adventures
|
||||
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')
|
||||
|
||||
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:
|
||||
# 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.")
|
||||
elif new_collection is 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.")
|
||||
|
||||
# Perform the update
|
||||
|
@ -94,11 +94,11 @@ class NoteViewSet(viewsets.ModelViewSet):
|
|||
|
||||
if new_collection is not None and new_collection!=instance.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.")
|
||||
elif new_collection is 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.")
|
||||
|
||||
# Perform the update
|
||||
|
@ -119,12 +119,12 @@ class NoteViewSet(viewsets.ModelViewSet):
|
|||
if collection:
|
||||
user = self.request.user
|
||||
# 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
|
||||
raise PermissionDenied("You do not have permission to use this 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
|
||||
|
||||
# Save the adventure with the current user as the owner
|
||||
serializer.save(user_id=self.request.user)
|
||||
serializer.save(user=self.request.user)
|
||||
|
|
|
@ -3,7 +3,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from worldtravel.models import Region, City, VisitedRegion, VisitedCity
|
||||
from adventures.models import Adventure
|
||||
from adventures.models import Location
|
||||
from adventures.serializers import AdventureSerializer
|
||||
import requests
|
||||
from adventures.geocoding import reverse_geocode
|
||||
|
@ -52,7 +52,7 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
|
|||
new_regions = {}
|
||||
new_city_count = 0
|
||||
new_cities = {}
|
||||
adventures = Adventure.objects.filter(user_id=self.request.user)
|
||||
adventures = Location.objects.filter(user=self.request.user)
|
||||
serializer = AdventureSerializer(adventures, many=True)
|
||||
for adventure, serialized_adventure in zip(adventures, serializer.data):
|
||||
if serialized_adventure['is_visited'] == True:
|
||||
|
@ -69,18 +69,18 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
|
|||
# data already contains region_id and city_id
|
||||
if 'region_id' in data and data['region_id'] is not None:
|
||||
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:
|
||||
visited_region = VisitedRegion(region=region, user_id=self.request.user)
|
||||
visited_region = VisitedRegion(region=region, user=self.request.user)
|
||||
visited_region.save()
|
||||
new_region_count += 1
|
||||
new_regions[region.id] = region.name
|
||||
|
||||
if 'city_id' in data and data['city_id'] is not None:
|
||||
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:
|
||||
visited_city = VisitedCity(city=city, user_id=self.request.user)
|
||||
visited_city = VisitedCity(city=city, user=self.request.user)
|
||||
visited_city.save()
|
||||
new_city_count += 1
|
||||
new_cities[city.id] = city.name
|
||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.decorators import action
|
||||
from django.shortcuts import get_object_or_404
|
||||
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 django.contrib.auth import get_user_model
|
||||
|
||||
|
@ -26,18 +26,18 @@ class StatsViewSet(viewsets.ViewSet):
|
|||
user.email = None
|
||||
|
||||
# get the counts for the user
|
||||
adventure_count = Adventure.objects.filter(
|
||||
user_id=user.id).count()
|
||||
adventure_count = Location.objects.filter(
|
||||
user=user.id).count()
|
||||
trips_count = Collection.objects.filter(
|
||||
user_id=user.id).count()
|
||||
user=user.id).count()
|
||||
visited_city_count = VisitedCity.objects.filter(
|
||||
user_id=user.id).count()
|
||||
user=user.id).count()
|
||||
total_cities = City.objects.count()
|
||||
visited_region_count = VisitedRegion.objects.filter(
|
||||
user_id=user.id).count()
|
||||
user=user.id).count()
|
||||
total_regions = Region.objects.count()
|
||||
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()
|
||||
return Response({
|
||||
'adventure_count': adventure_count,
|
||||
|
|
|
@ -17,7 +17,7 @@ class TransportationViewSet(viewsets.ModelViewSet):
|
|||
if not request.user.is_authenticated:
|
||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||
queryset = Transportation.objects.filter(
|
||||
Q(user_id=request.user.id)
|
||||
Q(user=request.user.id)
|
||||
)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
@ -27,11 +27,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
|
|||
if self.action == 'retrieve':
|
||||
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
|
||||
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')
|
||||
# For other actions, include user's own adventures and shared adventures
|
||||
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')
|
||||
|
||||
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:
|
||||
# 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.")
|
||||
elif new_collection is 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.")
|
||||
|
||||
# Perform the update
|
||||
|
@ -73,12 +73,12 @@ class TransportationViewSet(viewsets.ModelViewSet):
|
|||
if collection:
|
||||
user = self.request.user
|
||||
# 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
|
||||
raise PermissionDenied("You do not have permission to use this 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
|
||||
|
||||
# Save the adventure with the current user as the owner
|
||||
serializer.save(user_id=self.request.user)
|
||||
serializer.save(user=self.request.user)
|
|
@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated
|
|||
import requests
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from django.conf import settings
|
||||
from adventures.models import AdventureImage
|
||||
from adventures.models import LocationImage
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
import logging
|
||||
|
@ -253,11 +253,11 @@ class ImmichIntegrationView(viewsets.ViewSet):
|
|||
"""
|
||||
GET an Immich image using the integration and asset ID.
|
||||
Access levels (in order of priority):
|
||||
1. Public adventures: accessible by anyone
|
||||
2. Private adventures in public collections: accessible by anyone
|
||||
3. Private adventures in private collections shared with user: accessible by shared users
|
||||
4. Private adventures: accessible only to the owner
|
||||
5. No AdventureImage: owner can still view via integration
|
||||
1. Public locations: accessible by anyone
|
||||
2. Private locations in public collections: accessible by anyone
|
||||
3. Private locations in private collections shared with user: accessible by shared users
|
||||
4. Private locations: accessible only to the owner
|
||||
5. No LocationImage: owner can still view via integration
|
||||
"""
|
||||
if not imageid or not integration_id:
|
||||
return Response({
|
||||
|
@ -268,31 +268,31 @@ class ImmichIntegrationView(viewsets.ViewSet):
|
|||
|
||||
# Lookup integration and user
|
||||
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
|
||||
image_entry = (
|
||||
AdventureImage.objects
|
||||
.filter(immich_id=imageid, user_id=owner_id)
|
||||
.select_related('adventure')
|
||||
.prefetch_related('adventure__collections', 'adventure__collections__shared_with')
|
||||
.order_by('-adventure__is_public') # Public adventures first
|
||||
LocationImage.objects
|
||||
.filter(immich_id=imageid, user=owner_id)
|
||||
.select_related('location')
|
||||
.prefetch_related('location__collections', 'location__collections__shared_with')
|
||||
.order_by('-location__is_public') # Public locations first
|
||||
.first()
|
||||
)
|
||||
|
||||
# Access control
|
||||
if image_entry:
|
||||
adventure = image_entry.adventure
|
||||
collections = adventure.collections.all()
|
||||
|
||||
location = image_entry.location
|
||||
collections = location.collections.all()
|
||||
|
||||
# Determine access level
|
||||
is_authorized = False
|
||||
|
||||
# Level 1: Public adventure (highest priority)
|
||||
if adventure.is_public:
|
||||
|
||||
# Level 1: Public location (highest priority)
|
||||
if location.is_public:
|
||||
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):
|
||||
is_authorized = True
|
||||
|
||||
|
@ -308,15 +308,15 @@ class ImmichIntegrationView(viewsets.ViewSet):
|
|||
|
||||
if not is_authorized:
|
||||
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,
|
||||
'code': 'immich.permission_denied'
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
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:
|
||||
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,
|
||||
'code': 'immich.not_found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
|
|
@ -6,5 +6,5 @@ def get_user_uuid(user):
|
|||
class CustomModelSerializer(serializers.ModelSerializer):
|
||||
def to_representation(self, instance):
|
||||
representation = super().to_representation(instance)
|
||||
representation['user_id'] = get_user_uuid(instance.user_id)
|
||||
representation['user'] = get_user_uuid(instance.user)
|
||||
return representation
|
|
@ -12,7 +12,7 @@ from django.contrib.auth import get_user_model
|
|||
from .serializers import CustomUserDetailsSerializer as PublicUserSerializer
|
||||
from allauth.socialaccount.models import SocialApp
|
||||
from adventures.serializers import AdventureSerializer, CollectionSerializer
|
||||
from adventures.models import Adventure, Collection
|
||||
from adventures.models import Location, Collection
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
|
||||
User = get_user_model()
|
||||
|
@ -99,8 +99,8 @@ class PublicUserDetailView(APIView):
|
|||
user.email = None
|
||||
|
||||
# Get the users adventures and collections to include in the response
|
||||
adventures = Adventure.objects.filter(user_id=user, is_public=True)
|
||||
collections = Collection.objects.filter(user_id=user, is_public=True)
|
||||
adventures = Location.objects.filter(user=user, is_public=True)
|
||||
collections = Collection.objects.filter(user=user, is_public=True)
|
||||
adventure_serializer = AdventureSerializer(adventures, many=True)
|
||||
collection_serializer = CollectionSerializer(collections, many=True)
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from adventures.models import Adventure
|
||||
from adventures.models import Location
|
||||
import time
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Bulk geocode all adventures by triggering save on each one'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
adventures = Adventure.objects.all()
|
||||
adventures = Location.objects.all()
|
||||
total = adventures.count()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures'))
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -6,7 +6,7 @@ from django.contrib.gis.db import models as gis_models
|
|||
|
||||
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):
|
||||
|
||||
|
@ -50,29 +50,29 @@ class City(models.Model):
|
|||
|
||||
class VisitedRegion(models.Model):
|
||||
id = models.AutoField(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)
|
||||
region = models.ForeignKey(Region, on_delete=models.CASCADE)
|
||||
|
||||
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):
|
||||
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.")
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class VisitedCity(models.Model):
|
||||
id = models.AutoField(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)
|
||||
city = models.ForeignKey(City, on_delete=models.CASCADE)
|
||||
|
||||
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):
|
||||
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.")
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class CountrySerializer(serializers.ModelSerializer):
|
|||
user = getattr(request, 'user', None)
|
||||
|
||||
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
|
||||
|
||||
|
@ -62,8 +62,8 @@ class VisitedRegionSerializer(CustomModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = VisitedRegion
|
||||
fields = ['id', 'user_id', 'region', 'longitude', 'latitude', 'name']
|
||||
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name']
|
||||
fields = ['id', 'user', 'region', 'longitude', 'latitude', 'name']
|
||||
read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']
|
||||
|
||||
class VisitedCitySerializer(CustomModelSerializer):
|
||||
longitude = serializers.DecimalField(source='city.longitude', max_digits=9, decimal_places=6, read_only=True)
|
||||
|
@ -72,5 +72,5 @@ class VisitedCitySerializer(CustomModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = VisitedCity
|
||||
fields = ['id', 'user_id', 'city', 'longitude', 'latitude', 'name']
|
||||
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name']
|
||||
fields = ['id', 'user', 'city', 'longitude', 'latitude', 'name']
|
||||
read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']
|
|
@ -13,7 +13,7 @@ from django.contrib.gis.geos import Point
|
|||
from django.conf import settings
|
||||
from rest_framework.decorators import action
|
||||
from django.contrib.staticfiles import finders
|
||||
from adventures.models import Adventure
|
||||
from adventures.models import Location
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
|
@ -28,7 +28,7 @@ def regions_by_country(request, country_code):
|
|||
@permission_classes([IsAuthenticated])
|
||||
def visits_by_country(request, 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)
|
||||
return Response(serializer.data)
|
||||
|
@ -45,7 +45,7 @@ def cities_by_region(request, region_id):
|
|||
@permission_classes([IsAuthenticated])
|
||||
def visits_by_region(request, 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)
|
||||
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
|
||||
@action(detail=False, methods=['post'])
|
||||
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
|
||||
for adventure in adventures:
|
||||
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)
|
||||
region = Region.objects.filter(geometry__contains=point).first()
|
||||
if region:
|
||||
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists():
|
||||
VisitedRegion.objects.create(user_id=request.user, region=region)
|
||||
if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
|
||||
VisitedRegion.objects.create(user=request.user, region=region)
|
||||
count += 1
|
||||
except Exception as e:
|
||||
print(f"Error processing adventure {adventure.id}: {e}")
|
||||
|
@ -97,14 +97,14 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = [IsAuthenticated]
|
||||
|
||||
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):
|
||||
serializer.save(user_id=self.request.user)
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
request.data['user_id'] = request.user
|
||||
if VisitedRegion.objects.filter(user_id=request.user.id, region=request.data['region']).exists():
|
||||
request.data['user'] = request.user
|
||||
if VisitedRegion.objects.filter(user=request.user.id, region=request.data['region']).exists():
|
||||
return Response({"error": "Region already visited by user."}, status=400)
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
@ -115,7 +115,7 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
|
|||
def destroy(self, request, **kwargs):
|
||||
# delete by region id
|
||||
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():
|
||||
visited_region.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -127,27 +127,27 @@ class VisitedCityViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = [IsAuthenticated]
|
||||
|
||||
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):
|
||||
serializer.save(user_id=self.request.user)
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
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.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
# if the region is not visited, visit it
|
||||
region = serializer.validated_data['city'].region
|
||||
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists():
|
||||
VisitedRegion.objects.create(user_id=request.user, region=region)
|
||||
if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
|
||||
VisitedRegion.objects.create(user=request.user, region=region)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
def destroy(self, request, **kwargs):
|
||||
# delete by city id
|
||||
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():
|
||||
visited_city.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
@ -280,7 +280,7 @@
|
|||
{$t('adventures.open_details')}
|
||||
</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 tabindex="0" role="button" class="btn btn-square btn-sm btn-base-300">
|
||||
<DotsHorizontal class="w-5 h-5" />
|
||||
|
@ -297,7 +297,7 @@
|
|||
</button>
|
||||
</li>
|
||||
|
||||
{#if user?.uuid == adventure.user_id}
|
||||
{#if user?.uuid == adventure.user}
|
||||
<li>
|
||||
<button
|
||||
on:click={() => (isCollectionModalOpen = true)}
|
||||
|
|
|
@ -110,13 +110,13 @@
|
|||
longitude: NaN,
|
||||
location: null,
|
||||
images: [],
|
||||
user_id: null,
|
||||
user: null,
|
||||
category: {
|
||||
id: '',
|
||||
name: '',
|
||||
display_name: '',
|
||||
icon: '',
|
||||
user_id: ''
|
||||
user: ''
|
||||
},
|
||||
attachments: []
|
||||
};
|
||||
|
@ -135,7 +135,7 @@
|
|||
longitude: adventureToEdit?.longitude || NaN,
|
||||
location: adventureToEdit?.location || null,
|
||||
images: adventureToEdit?.images || [],
|
||||
user_id: adventureToEdit?.user_id || null,
|
||||
user: adventureToEdit?.user || null,
|
||||
visits: adventureToEdit?.visits || [],
|
||||
is_visited: adventureToEdit?.is_visited || false,
|
||||
category: adventureToEdit?.category || {
|
||||
|
@ -143,7 +143,7 @@
|
|||
name: '',
|
||||
display_name: '',
|
||||
icon: '',
|
||||
user_id: ''
|
||||
user: ''
|
||||
},
|
||||
|
||||
attachments: adventureToEdit?.attachments || []
|
||||
|
@ -245,7 +245,7 @@
|
|||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('adventure', adventure.id);
|
||||
formData.append('location', adventure.id);
|
||||
formData.append('name', attachmentName);
|
||||
|
||||
try {
|
||||
|
@ -451,7 +451,7 @@
|
|||
name: 'general',
|
||||
display_name: 'General',
|
||||
icon: '🌍',
|
||||
user_id: ''
|
||||
user: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
display_name: '',
|
||||
icon: '',
|
||||
id: '',
|
||||
user_id: '',
|
||||
user: '',
|
||||
num_adventures: 0
|
||||
};
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
<Launch class="w-5 h-5" />
|
||||
{$t('notes.open')}
|
||||
</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
|
||||
id="delete_adventure"
|
||||
data-umami-event="Delete Checklist"
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
let warning: string | null = '';
|
||||
|
||||
let isReadOnly =
|
||||
!(checklist && user?.uuid == checklist?.user_id) &&
|
||||
!(checklist && user?.uuid == checklist?.user) &&
|
||||
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
|
||||
!!checklist;
|
||||
let newStatus: boolean = false;
|
||||
|
@ -40,7 +40,7 @@
|
|||
name: newItem,
|
||||
is_checked: newStatus,
|
||||
id: '',
|
||||
user_id: '',
|
||||
user: '',
|
||||
checklist: 0,
|
||||
created_at: '',
|
||||
updated_at: ''
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
<Launch class="w-4 h-4" />
|
||||
{$t('adventures.open_details')}
|
||||
</button>
|
||||
{#if user && user.uuid == collection.user_id}
|
||||
{#if user && user.uuid == collection.user}
|
||||
<div class="dropdown dropdown-end">
|
||||
<button type="button" class="btn btn-square btn-sm btn-base-300">
|
||||
<DotsHorizontal class="w-5 h-5" />
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
description: collectionToEdit?.description || '',
|
||||
start_date: collectionToEdit?.start_date || null,
|
||||
end_date: collectionToEdit?.end_date || null,
|
||||
user_id: collectionToEdit?.user_id || '',
|
||||
user: collectionToEdit?.user || '',
|
||||
is_public: collectionToEdit?.is_public || false,
|
||||
adventures: collectionToEdit?.adventures || [],
|
||||
link: collectionToEdit?.link || '',
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 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">
|
||||
{#if lodging.reservation_number}
|
||||
<div class="flex items-center gap-2">
|
||||
|
@ -174,7 +174,7 @@
|
|||
{/if}
|
||||
|
||||
<!-- 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">
|
||||
<button
|
||||
class="btn btn-neutral btn-sm flex items-center gap-1"
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
function initializeLodging(lodgingToEdit: Lodging | null): Lodging {
|
||||
return {
|
||||
id: lodgingToEdit?.id || '',
|
||||
user_id: lodgingToEdit?.user_id || '',
|
||||
user: lodgingToEdit?.user || '',
|
||||
name: lodgingToEdit?.name || '',
|
||||
type: lodgingToEdit?.type || 'other',
|
||||
description: lodgingToEdit?.description || '',
|
||||
|
|
|
@ -124,7 +124,7 @@
|
|||
<Launch class="w-5 h-5" />
|
||||
{$t('notes.open')}
|
||||
</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
|
||||
id="delete_adventure"
|
||||
data-umami-event="Delete Adventure"
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
let constrainDates: boolean = false;
|
||||
|
||||
let isReadOnly =
|
||||
!(note && user?.uuid == note?.user_id) &&
|
||||
!(note && user?.uuid == note?.user) &&
|
||||
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
|
||||
!!note;
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<button
|
||||
class="btn btn-neutral btn-sm flex items-center gap-1"
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
flight_number: transportationToEdit?.flight_number || '',
|
||||
from_location: transportationToEdit?.from_location || '',
|
||||
to_location: transportationToEdit?.to_location || '',
|
||||
user_id: transportationToEdit?.user_id || '',
|
||||
user: transportationToEdit?.user || '',
|
||||
is_public: transportationToEdit?.is_public || false,
|
||||
collection: transportationToEdit?.collection || collection.id,
|
||||
created_at: transportationToEdit?.created_at || '',
|
||||
|
|
|
@ -16,7 +16,7 @@ export type User = {
|
|||
|
||||
export type Adventure = {
|
||||
id: string;
|
||||
user_id: string | null;
|
||||
user: string | null;
|
||||
name: string;
|
||||
location?: string | null;
|
||||
activity_types?: string[] | null;
|
||||
|
@ -96,7 +96,7 @@ export type City = {
|
|||
export type VisitedRegion = {
|
||||
id: number;
|
||||
region: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
name: string;
|
||||
|
@ -105,7 +105,7 @@ export type VisitedRegion = {
|
|||
export type VisitedCity = {
|
||||
id: number;
|
||||
city: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
name: string;
|
||||
|
@ -123,7 +123,7 @@ export type Point = {
|
|||
|
||||
export type Collection = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
name: string;
|
||||
description: string;
|
||||
is_public: boolean;
|
||||
|
@ -153,7 +153,7 @@ export type GeocodeSearchResult = {
|
|||
|
||||
export type Transportation = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
type: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
|
@ -179,7 +179,7 @@ export type Transportation = {
|
|||
|
||||
export type Note = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
name: string;
|
||||
content: string | null;
|
||||
links: string[] | null;
|
||||
|
@ -192,7 +192,7 @@ export type Note = {
|
|||
|
||||
export type Checklist = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
name: string;
|
||||
items: ChecklistItem[];
|
||||
date: string | null; // ISO 8601 date string
|
||||
|
@ -204,7 +204,7 @@ export type Checklist = {
|
|||
|
||||
export type ChecklistItem = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
name: string;
|
||||
is_checked: boolean;
|
||||
checklist: number;
|
||||
|
@ -235,7 +235,7 @@ export type Category = {
|
|||
name: string;
|
||||
display_name: string;
|
||||
icon: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
num_adventures?: number | null;
|
||||
};
|
||||
|
||||
|
@ -279,13 +279,13 @@ export type Attachment = {
|
|||
file: string;
|
||||
adventure: string;
|
||||
extension: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Lodging = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user: string;
|
||||
name: string;
|
||||
type: string;
|
||||
description: string | null;
|
||||
|
|
|
@ -150,7 +150,7 @@
|
|||
{/if}
|
||||
|
||||
{#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">
|
||||
<button
|
||||
class="btn btn-primary btn-circle w-16 h-16 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-110"
|
||||
|
|
|
@ -498,7 +498,7 @@
|
|||
function recomendationToAdventure(recomendation: any) {
|
||||
adventureToEdit = {
|
||||
id: '',
|
||||
user_id: null,
|
||||
user: null,
|
||||
name: recomendation.name,
|
||||
latitude: recomendation.latitude,
|
||||
longitude: recomendation.longitude,
|
||||
|
@ -513,7 +513,7 @@
|
|||
icon: osmTagToEmoji(recomendation.tag),
|
||||
id: '',
|
||||
name: recomendation.tag,
|
||||
user_id: ''
|
||||
user: ''
|
||||
},
|
||||
attachments: []
|
||||
};
|
||||
|
@ -748,7 +748,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
{#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="flex flex-row items-center justify-center gap-4">
|
||||
<div class="dropdown dropdown-top dropdown-end z-[999]">
|
||||
|
@ -760,7 +760,7 @@
|
|||
tabindex="0"
|
||||
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>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue