mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-05 13:15:18 +02:00
Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components. - Adjusted queries and filters to reflect the new user field naming convention. - Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities. - Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
This commit is contained in:
parent
7a17e0e1d8
commit
241d27a1a6
50 changed files with 918 additions and 792 deletions
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category, Attachment, Lodging
|
from .models import Location, Checklist, ChecklistItem, Collection, Transportation, Note, LocationImage, Visit, Category, Attachment, Lodging
|
||||||
from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity
|
from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity
|
||||||
from allauth.account.decorators import secure_admin_login
|
from allauth.account.decorators import secure_admin_login
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ def trigger_geocoding(modeladmin, request, queryset):
|
||||||
|
|
||||||
|
|
||||||
class AdventureAdmin(admin.ModelAdmin):
|
class AdventureAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'get_category', 'get_visit_count', 'user_id', 'is_public')
|
list_display = ('name', 'get_category', 'get_visit_count', 'user', 'is_public')
|
||||||
list_filter = ( 'user_id', 'is_public')
|
list_filter = ( 'user', 'is_public')
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
readonly_fields = ('city', 'region', 'country')
|
readonly_fields = ('city', 'region', 'country')
|
||||||
actions = [trigger_geocoding]
|
actions = [trigger_geocoding]
|
||||||
|
@ -97,7 +97,7 @@ class CustomUserAdmin(UserAdmin):
|
||||||
return
|
return
|
||||||
|
|
||||||
class AdventureImageAdmin(admin.ModelAdmin):
|
class AdventureImageAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user_id', 'image_display')
|
list_display = ('user', 'image_display')
|
||||||
|
|
||||||
def image_display(self, obj):
|
def image_display(self, obj):
|
||||||
if obj.image:
|
if obj.image:
|
||||||
|
@ -109,7 +109,7 @@ class AdventureImageAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class VisitAdmin(admin.ModelAdmin):
|
class VisitAdmin(admin.ModelAdmin):
|
||||||
list_display = ('adventure', 'start_date', 'end_date', 'notes')
|
list_display = ('location', 'start_date', 'end_date', 'notes')
|
||||||
list_filter = ('start_date', 'end_date')
|
list_filter = ('start_date', 'end_date')
|
||||||
search_fields = ('notes',)
|
search_fields = ('notes',)
|
||||||
|
|
||||||
|
@ -125,19 +125,19 @@ class VisitAdmin(admin.ModelAdmin):
|
||||||
image_display.short_description = 'Image Preview'
|
image_display.short_description = 'Image Preview'
|
||||||
|
|
||||||
class CategoryAdmin(admin.ModelAdmin):
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'user_id', 'display_name', 'icon')
|
list_display = ('name', 'user', 'display_name', 'icon')
|
||||||
search_fields = ('name', 'display_name')
|
search_fields = ('name', 'display_name')
|
||||||
|
|
||||||
class CollectionAdmin(admin.ModelAdmin):
|
class CollectionAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
list_display = ('name', 'user_id', 'is_public')
|
list_display = ('name', 'user', 'is_public')
|
||||||
|
|
||||||
admin.site.register(CustomUser, CustomUserAdmin)
|
admin.site.register(CustomUser, CustomUserAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Adventure, AdventureAdmin)
|
admin.site.register(Location, AdventureAdmin)
|
||||||
admin.site.register(Collection, CollectionAdmin)
|
admin.site.register(Collection, CollectionAdmin)
|
||||||
admin.site.register(Visit, VisitAdmin)
|
admin.site.register(Visit, VisitAdmin)
|
||||||
admin.site.register(Country, CountryAdmin)
|
admin.site.register(Country, CountryAdmin)
|
||||||
|
@ -147,7 +147,7 @@ admin.site.register(Transportation)
|
||||||
admin.site.register(Note)
|
admin.site.register(Note)
|
||||||
admin.site.register(Checklist)
|
admin.site.register(Checklist)
|
||||||
admin.site.register(ChecklistItem)
|
admin.site.register(ChecklistItem)
|
||||||
admin.site.register(AdventureImage, AdventureImageAdmin)
|
admin.site.register(LocationImage, AdventureImageAdmin)
|
||||||
admin.site.register(Category, CategoryAdmin)
|
admin.site.register(Category, CategoryAdmin)
|
||||||
admin.site.register(City, CityAdmin)
|
admin.site.register(City, CityAdmin)
|
||||||
admin.site.register(VisitedCity)
|
admin.site.register(VisitedCity)
|
||||||
|
|
|
@ -167,7 +167,7 @@ def extractIsoCode(user, data):
|
||||||
return {"error": "No region found"}
|
return {"error": "No region found"}
|
||||||
|
|
||||||
region = Region.objects.filter(id=iso_code).first()
|
region = Region.objects.filter(id=iso_code).first()
|
||||||
visited_region = VisitedRegion.objects.filter(region=region, user_id=user).first()
|
visited_region = VisitedRegion.objects.filter(region=region, user=user).first()
|
||||||
|
|
||||||
region_visited = False
|
region_visited = False
|
||||||
city_visited = False
|
city_visited = False
|
||||||
|
@ -177,7 +177,7 @@ def extractIsoCode(user, data):
|
||||||
if town_city_or_county:
|
if town_city_or_county:
|
||||||
display_name = f"{town_city_or_county}, {region.name}, {country_code}"
|
display_name = f"{town_city_or_county}, {region.name}, {country_code}"
|
||||||
city = City.objects.filter(name__contains=town_city_or_county, region=region).first()
|
city = City.objects.filter(name__contains=town_city_or_county, region=region).first()
|
||||||
visited_city = VisitedCity.objects.filter(city=city, user_id=user).first()
|
visited_city = VisitedCity.objects.filter(city=city, user=user).first()
|
||||||
|
|
||||||
if visited_region:
|
if visited_region:
|
||||||
region_visited = True
|
region_visited = True
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from adventures.models import Adventure
|
from adventures.models import Location
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
@ -38,8 +38,8 @@ class Command(BaseCommand):
|
||||||
]
|
]
|
||||||
|
|
||||||
for name, location, type_ in adventures:
|
for name, location, type_ in adventures:
|
||||||
Adventure.objects.create(
|
Location.objects.create(
|
||||||
user_id=user,
|
user=user,
|
||||||
name=name,
|
name=name,
|
||||||
location=location,
|
location=location,
|
||||||
type=type_,
|
type=type_,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
class AdventureManager(models.Manager):
|
class LocationManager(models.Manager):
|
||||||
def retrieve_adventures(self, user, include_owned=False, include_shared=False, include_public=False):
|
def retrieve_adventures(self, user, include_owned=False, include_shared=False, include_public=False):
|
||||||
query = Q()
|
query = Q()
|
||||||
|
|
||||||
if include_owned:
|
if include_owned:
|
||||||
query |= Q(user_id=user)
|
query |= Q(user=user)
|
||||||
|
|
||||||
if include_shared:
|
if include_shared:
|
||||||
query |= Q(collections__shared_with=user)
|
query |= Q(collections__shared_with=user)
|
||||||
|
|
|
@ -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
|
from django.core.exceptions import ValidationError
|
||||||
import os
|
import os
|
||||||
from typing import Iterable
|
|
||||||
import uuid
|
import uuid
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
from adventures.managers import AdventureManager
|
from adventures.managers import LocationManager
|
||||||
import threading
|
import threading
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
@ -13,46 +12,47 @@ from django_resized import ResizedImageField
|
||||||
from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion
|
from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from adventures.utils.timezones import TIMEZONES
|
||||||
|
|
||||||
def background_geocode_and_assign(adventure_id: str):
|
def background_geocode_and_assign(location_id: str):
|
||||||
print(f"[Adventure Geocode Thread] Starting geocode for adventure {adventure_id}")
|
print(f"[Location Geocode Thread] Starting geocode for location {location_id}")
|
||||||
try:
|
try:
|
||||||
adventure = Adventure.objects.get(id=adventure_id)
|
location = Location.objects.get(id=location_id)
|
||||||
if not (adventure.latitude and adventure.longitude):
|
if not (location.latitude and location.longitude):
|
||||||
return
|
return
|
||||||
|
|
||||||
from adventures.geocoding import reverse_geocode # or wherever you defined it
|
from adventures.geocoding import reverse_geocode # or wherever you defined it
|
||||||
is_visited = adventure.is_visited_status()
|
is_visited = location.is_visited_status()
|
||||||
result = reverse_geocode(adventure.latitude, adventure.longitude, adventure.user_id)
|
result = reverse_geocode(location.latitude, location.longitude, location.user)
|
||||||
|
|
||||||
if 'region_id' in result:
|
if 'region_id' in result:
|
||||||
region = Region.objects.filter(id=result['region_id']).first()
|
region = Region.objects.filter(id=result['region_id']).first()
|
||||||
if region:
|
if region:
|
||||||
adventure.region = region
|
location.region = region
|
||||||
if is_visited:
|
if is_visited:
|
||||||
VisitedRegion.objects.get_or_create(user_id=adventure.user_id, region=region)
|
VisitedRegion.objects.get_or_create(user=location.user, region=region)
|
||||||
|
|
||||||
if 'city_id' in result:
|
if 'city_id' in result:
|
||||||
city = City.objects.filter(id=result['city_id']).first()
|
city = City.objects.filter(id=result['city_id']).first()
|
||||||
if city:
|
if city:
|
||||||
adventure.city = city
|
location.city = city
|
||||||
if is_visited:
|
if is_visited:
|
||||||
VisitedCity.objects.get_or_create(user_id=adventure.user_id, city=city)
|
VisitedCity.objects.get_or_create(user=location.user, city=city)
|
||||||
|
|
||||||
if 'country_id' in result:
|
if 'country_id' in result:
|
||||||
country = Country.objects.filter(country_code=result['country_id']).first()
|
country = Country.objects.filter(country_code=result['country_id']).first()
|
||||||
if country:
|
if country:
|
||||||
adventure.country = country
|
location.country = country
|
||||||
|
|
||||||
# Save updated location info
|
# Save updated location info
|
||||||
# Save updated location info, skip geocode threading
|
# Save updated location info, skip geocode threading
|
||||||
adventure.save(update_fields=["region", "city", "country"], _skip_geocode=True)
|
location.save(update_fields=["region", "city", "country"], _skip_geocode=True)
|
||||||
|
|
||||||
# print(f"[Adventure Geocode Thread] Successfully processed {adventure_id}: {adventure.name} - {adventure.latitude}, {adventure.longitude}")
|
# print(f"[Adventure Geocode Thread] Successfully processed {adventure_id}: {adventure.name} - {adventure.latitude}, {adventure.longitude}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Optional: log or print the error
|
# Optional: log or print the error
|
||||||
print(f"[Adventure Geocode Thread] Error processing {adventure_id}: {e}")
|
print(f"[Location Geocode Thread] Error processing {location_id}: {e}")
|
||||||
|
|
||||||
def validate_file_extension(value):
|
def validate_file_extension(value):
|
||||||
import os
|
import os
|
||||||
|
@ -62,6 +62,7 @@ def validate_file_extension(value):
|
||||||
if not ext.lower() in valid_extensions:
|
if not ext.lower() in valid_extensions:
|
||||||
raise ValidationError('Unsupported file extension.')
|
raise ValidationError('Unsupported file extension.')
|
||||||
|
|
||||||
|
# Legacy support for old adventure types, not used in newer versions since custom categories are now used
|
||||||
ADVENTURE_TYPES = [
|
ADVENTURE_TYPES = [
|
||||||
('general', 'General 🌍'),
|
('general', 'General 🌍'),
|
||||||
('outdoor', 'Outdoor 🏞️'),
|
('outdoor', 'Outdoor 🏞️'),
|
||||||
|
@ -87,426 +88,6 @@ ADVENTURE_TYPES = [
|
||||||
('other', 'Other')
|
('other', 'Other')
|
||||||
]
|
]
|
||||||
|
|
||||||
TIMEZONES = [
|
|
||||||
"Africa/Abidjan",
|
|
||||||
"Africa/Accra",
|
|
||||||
"Africa/Addis_Ababa",
|
|
||||||
"Africa/Algiers",
|
|
||||||
"Africa/Asmera",
|
|
||||||
"Africa/Bamako",
|
|
||||||
"Africa/Bangui",
|
|
||||||
"Africa/Banjul",
|
|
||||||
"Africa/Bissau",
|
|
||||||
"Africa/Blantyre",
|
|
||||||
"Africa/Brazzaville",
|
|
||||||
"Africa/Bujumbura",
|
|
||||||
"Africa/Cairo",
|
|
||||||
"Africa/Casablanca",
|
|
||||||
"Africa/Ceuta",
|
|
||||||
"Africa/Conakry",
|
|
||||||
"Africa/Dakar",
|
|
||||||
"Africa/Dar_es_Salaam",
|
|
||||||
"Africa/Djibouti",
|
|
||||||
"Africa/Douala",
|
|
||||||
"Africa/El_Aaiun",
|
|
||||||
"Africa/Freetown",
|
|
||||||
"Africa/Gaborone",
|
|
||||||
"Africa/Harare",
|
|
||||||
"Africa/Johannesburg",
|
|
||||||
"Africa/Juba",
|
|
||||||
"Africa/Kampala",
|
|
||||||
"Africa/Khartoum",
|
|
||||||
"Africa/Kigali",
|
|
||||||
"Africa/Kinshasa",
|
|
||||||
"Africa/Lagos",
|
|
||||||
"Africa/Libreville",
|
|
||||||
"Africa/Lome",
|
|
||||||
"Africa/Luanda",
|
|
||||||
"Africa/Lubumbashi",
|
|
||||||
"Africa/Lusaka",
|
|
||||||
"Africa/Malabo",
|
|
||||||
"Africa/Maputo",
|
|
||||||
"Africa/Maseru",
|
|
||||||
"Africa/Mbabane",
|
|
||||||
"Africa/Mogadishu",
|
|
||||||
"Africa/Monrovia",
|
|
||||||
"Africa/Nairobi",
|
|
||||||
"Africa/Ndjamena",
|
|
||||||
"Africa/Niamey",
|
|
||||||
"Africa/Nouakchott",
|
|
||||||
"Africa/Ouagadougou",
|
|
||||||
"Africa/Porto-Novo",
|
|
||||||
"Africa/Sao_Tome",
|
|
||||||
"Africa/Tripoli",
|
|
||||||
"Africa/Tunis",
|
|
||||||
"Africa/Windhoek",
|
|
||||||
"America/Adak",
|
|
||||||
"America/Anchorage",
|
|
||||||
"America/Anguilla",
|
|
||||||
"America/Antigua",
|
|
||||||
"America/Araguaina",
|
|
||||||
"America/Argentina/La_Rioja",
|
|
||||||
"America/Argentina/Rio_Gallegos",
|
|
||||||
"America/Argentina/Salta",
|
|
||||||
"America/Argentina/San_Juan",
|
|
||||||
"America/Argentina/San_Luis",
|
|
||||||
"America/Argentina/Tucuman",
|
|
||||||
"America/Argentina/Ushuaia",
|
|
||||||
"America/Aruba",
|
|
||||||
"America/Asuncion",
|
|
||||||
"America/Bahia",
|
|
||||||
"America/Bahia_Banderas",
|
|
||||||
"America/Barbados",
|
|
||||||
"America/Belem",
|
|
||||||
"America/Belize",
|
|
||||||
"America/Blanc-Sablon",
|
|
||||||
"America/Boa_Vista",
|
|
||||||
"America/Bogota",
|
|
||||||
"America/Boise",
|
|
||||||
"America/Buenos_Aires",
|
|
||||||
"America/Cambridge_Bay",
|
|
||||||
"America/Campo_Grande",
|
|
||||||
"America/Cancun",
|
|
||||||
"America/Caracas",
|
|
||||||
"America/Catamarca",
|
|
||||||
"America/Cayenne",
|
|
||||||
"America/Cayman",
|
|
||||||
"America/Chicago",
|
|
||||||
"America/Chihuahua",
|
|
||||||
"America/Ciudad_Juarez",
|
|
||||||
"America/Coral_Harbour",
|
|
||||||
"America/Cordoba",
|
|
||||||
"America/Costa_Rica",
|
|
||||||
"America/Creston",
|
|
||||||
"America/Cuiaba",
|
|
||||||
"America/Curacao",
|
|
||||||
"America/Danmarkshavn",
|
|
||||||
"America/Dawson",
|
|
||||||
"America/Dawson_Creek",
|
|
||||||
"America/Denver",
|
|
||||||
"America/Detroit",
|
|
||||||
"America/Dominica",
|
|
||||||
"America/Edmonton",
|
|
||||||
"America/Eirunepe",
|
|
||||||
"America/El_Salvador",
|
|
||||||
"America/Fort_Nelson",
|
|
||||||
"America/Fortaleza",
|
|
||||||
"America/Glace_Bay",
|
|
||||||
"America/Godthab",
|
|
||||||
"America/Goose_Bay",
|
|
||||||
"America/Grand_Turk",
|
|
||||||
"America/Grenada",
|
|
||||||
"America/Guadeloupe",
|
|
||||||
"America/Guatemala",
|
|
||||||
"America/Guayaquil",
|
|
||||||
"America/Guyana",
|
|
||||||
"America/Halifax",
|
|
||||||
"America/Havana",
|
|
||||||
"America/Hermosillo",
|
|
||||||
"America/Indiana/Knox",
|
|
||||||
"America/Indiana/Marengo",
|
|
||||||
"America/Indiana/Petersburg",
|
|
||||||
"America/Indiana/Tell_City",
|
|
||||||
"America/Indiana/Vevay",
|
|
||||||
"America/Indiana/Vincennes",
|
|
||||||
"America/Indiana/Winamac",
|
|
||||||
"America/Indianapolis",
|
|
||||||
"America/Inuvik",
|
|
||||||
"America/Iqaluit",
|
|
||||||
"America/Jamaica",
|
|
||||||
"America/Jujuy",
|
|
||||||
"America/Juneau",
|
|
||||||
"America/Kentucky/Monticello",
|
|
||||||
"America/Kralendijk",
|
|
||||||
"America/La_Paz",
|
|
||||||
"America/Lima",
|
|
||||||
"America/Los_Angeles",
|
|
||||||
"America/Louisville",
|
|
||||||
"America/Lower_Princes",
|
|
||||||
"America/Maceio",
|
|
||||||
"America/Managua",
|
|
||||||
"America/Manaus",
|
|
||||||
"America/Marigot",
|
|
||||||
"America/Martinique",
|
|
||||||
"America/Matamoros",
|
|
||||||
"America/Mazatlan",
|
|
||||||
"America/Mendoza",
|
|
||||||
"America/Menominee",
|
|
||||||
"America/Merida",
|
|
||||||
"America/Metlakatla",
|
|
||||||
"America/Mexico_City",
|
|
||||||
"America/Miquelon",
|
|
||||||
"America/Moncton",
|
|
||||||
"America/Monterrey",
|
|
||||||
"America/Montevideo",
|
|
||||||
"America/Montserrat",
|
|
||||||
"America/Nassau",
|
|
||||||
"America/New_York",
|
|
||||||
"America/Nome",
|
|
||||||
"America/Noronha",
|
|
||||||
"America/North_Dakota/Beulah",
|
|
||||||
"America/North_Dakota/Center",
|
|
||||||
"America/North_Dakota/New_Salem",
|
|
||||||
"America/Ojinaga",
|
|
||||||
"America/Panama",
|
|
||||||
"America/Paramaribo",
|
|
||||||
"America/Phoenix",
|
|
||||||
"America/Port-au-Prince",
|
|
||||||
"America/Port_of_Spain",
|
|
||||||
"America/Porto_Velho",
|
|
||||||
"America/Puerto_Rico",
|
|
||||||
"America/Punta_Arenas",
|
|
||||||
"America/Rankin_Inlet",
|
|
||||||
"America/Recife",
|
|
||||||
"America/Regina",
|
|
||||||
"America/Resolute",
|
|
||||||
"America/Rio_Branco",
|
|
||||||
"America/Santarem",
|
|
||||||
"America/Santiago",
|
|
||||||
"America/Santo_Domingo",
|
|
||||||
"America/Sao_Paulo",
|
|
||||||
"America/Scoresbysund",
|
|
||||||
"America/Sitka",
|
|
||||||
"America/St_Barthelemy",
|
|
||||||
"America/St_Johns",
|
|
||||||
"America/St_Kitts",
|
|
||||||
"America/St_Lucia",
|
|
||||||
"America/St_Thomas",
|
|
||||||
"America/St_Vincent",
|
|
||||||
"America/Swift_Current",
|
|
||||||
"America/Tegucigalpa",
|
|
||||||
"America/Thule",
|
|
||||||
"America/Tijuana",
|
|
||||||
"America/Toronto",
|
|
||||||
"America/Tortola",
|
|
||||||
"America/Vancouver",
|
|
||||||
"America/Whitehorse",
|
|
||||||
"America/Winnipeg",
|
|
||||||
"America/Yakutat",
|
|
||||||
"Antarctica/Casey",
|
|
||||||
"Antarctica/Davis",
|
|
||||||
"Antarctica/DumontDUrville",
|
|
||||||
"Antarctica/Macquarie",
|
|
||||||
"Antarctica/Mawson",
|
|
||||||
"Antarctica/McMurdo",
|
|
||||||
"Antarctica/Palmer",
|
|
||||||
"Antarctica/Rothera",
|
|
||||||
"Antarctica/Syowa",
|
|
||||||
"Antarctica/Troll",
|
|
||||||
"Antarctica/Vostok",
|
|
||||||
"Arctic/Longyearbyen",
|
|
||||||
"Asia/Aden",
|
|
||||||
"Asia/Almaty",
|
|
||||||
"Asia/Amman",
|
|
||||||
"Asia/Anadyr",
|
|
||||||
"Asia/Aqtau",
|
|
||||||
"Asia/Aqtobe",
|
|
||||||
"Asia/Ashgabat",
|
|
||||||
"Asia/Atyrau",
|
|
||||||
"Asia/Baghdad",
|
|
||||||
"Asia/Bahrain",
|
|
||||||
"Asia/Baku",
|
|
||||||
"Asia/Bangkok",
|
|
||||||
"Asia/Barnaul",
|
|
||||||
"Asia/Beirut",
|
|
||||||
"Asia/Bishkek",
|
|
||||||
"Asia/Brunei",
|
|
||||||
"Asia/Calcutta",
|
|
||||||
"Asia/Chita",
|
|
||||||
"Asia/Colombo",
|
|
||||||
"Asia/Damascus",
|
|
||||||
"Asia/Dhaka",
|
|
||||||
"Asia/Dili",
|
|
||||||
"Asia/Dubai",
|
|
||||||
"Asia/Dushanbe",
|
|
||||||
"Asia/Famagusta",
|
|
||||||
"Asia/Gaza",
|
|
||||||
"Asia/Hebron",
|
|
||||||
"Asia/Hong_Kong",
|
|
||||||
"Asia/Hovd",
|
|
||||||
"Asia/Irkutsk",
|
|
||||||
"Asia/Jakarta",
|
|
||||||
"Asia/Jayapura",
|
|
||||||
"Asia/Jerusalem",
|
|
||||||
"Asia/Kabul",
|
|
||||||
"Asia/Kamchatka",
|
|
||||||
"Asia/Karachi",
|
|
||||||
"Asia/Katmandu",
|
|
||||||
"Asia/Khandyga",
|
|
||||||
"Asia/Krasnoyarsk",
|
|
||||||
"Asia/Kuala_Lumpur",
|
|
||||||
"Asia/Kuching",
|
|
||||||
"Asia/Kuwait",
|
|
||||||
"Asia/Macau",
|
|
||||||
"Asia/Magadan",
|
|
||||||
"Asia/Makassar",
|
|
||||||
"Asia/Manila",
|
|
||||||
"Asia/Muscat",
|
|
||||||
"Asia/Nicosia",
|
|
||||||
"Asia/Novokuznetsk",
|
|
||||||
"Asia/Novosibirsk",
|
|
||||||
"Asia/Omsk",
|
|
||||||
"Asia/Oral",
|
|
||||||
"Asia/Phnom_Penh",
|
|
||||||
"Asia/Pontianak",
|
|
||||||
"Asia/Pyongyang",
|
|
||||||
"Asia/Qatar",
|
|
||||||
"Asia/Qostanay",
|
|
||||||
"Asia/Qyzylorda",
|
|
||||||
"Asia/Rangoon",
|
|
||||||
"Asia/Riyadh",
|
|
||||||
"Asia/Saigon",
|
|
||||||
"Asia/Sakhalin",
|
|
||||||
"Asia/Samarkand",
|
|
||||||
"Asia/Seoul",
|
|
||||||
"Asia/Shanghai",
|
|
||||||
"Asia/Singapore",
|
|
||||||
"Asia/Srednekolymsk",
|
|
||||||
"Asia/Taipei",
|
|
||||||
"Asia/Tashkent",
|
|
||||||
"Asia/Tbilisi",
|
|
||||||
"Asia/Tehran",
|
|
||||||
"Asia/Thimphu",
|
|
||||||
"Asia/Tokyo",
|
|
||||||
"Asia/Tomsk",
|
|
||||||
"Asia/Ulaanbaatar",
|
|
||||||
"Asia/Urumqi",
|
|
||||||
"Asia/Ust-Nera",
|
|
||||||
"Asia/Vientiane",
|
|
||||||
"Asia/Vladivostok",
|
|
||||||
"Asia/Yakutsk",
|
|
||||||
"Asia/Yekaterinburg",
|
|
||||||
"Asia/Yerevan",
|
|
||||||
"Atlantic/Azores",
|
|
||||||
"Atlantic/Bermuda",
|
|
||||||
"Atlantic/Canary",
|
|
||||||
"Atlantic/Cape_Verde",
|
|
||||||
"Atlantic/Faeroe",
|
|
||||||
"Atlantic/Madeira",
|
|
||||||
"Atlantic/Reykjavik",
|
|
||||||
"Atlantic/South_Georgia",
|
|
||||||
"Atlantic/St_Helena",
|
|
||||||
"Atlantic/Stanley",
|
|
||||||
"Australia/Adelaide",
|
|
||||||
"Australia/Brisbane",
|
|
||||||
"Australia/Broken_Hill",
|
|
||||||
"Australia/Darwin",
|
|
||||||
"Australia/Eucla",
|
|
||||||
"Australia/Hobart",
|
|
||||||
"Australia/Lindeman",
|
|
||||||
"Australia/Lord_Howe",
|
|
||||||
"Australia/Melbourne",
|
|
||||||
"Australia/Perth",
|
|
||||||
"Australia/Sydney",
|
|
||||||
"Europe/Amsterdam",
|
|
||||||
"Europe/Andorra",
|
|
||||||
"Europe/Astrakhan",
|
|
||||||
"Europe/Athens",
|
|
||||||
"Europe/Belgrade",
|
|
||||||
"Europe/Berlin",
|
|
||||||
"Europe/Bratislava",
|
|
||||||
"Europe/Brussels",
|
|
||||||
"Europe/Bucharest",
|
|
||||||
"Europe/Budapest",
|
|
||||||
"Europe/Busingen",
|
|
||||||
"Europe/Chisinau",
|
|
||||||
"Europe/Copenhagen",
|
|
||||||
"Europe/Dublin",
|
|
||||||
"Europe/Gibraltar",
|
|
||||||
"Europe/Guernsey",
|
|
||||||
"Europe/Helsinki",
|
|
||||||
"Europe/Isle_of_Man",
|
|
||||||
"Europe/Istanbul",
|
|
||||||
"Europe/Jersey",
|
|
||||||
"Europe/Kaliningrad",
|
|
||||||
"Europe/Kiev",
|
|
||||||
"Europe/Kirov",
|
|
||||||
"Europe/Lisbon",
|
|
||||||
"Europe/Ljubljana",
|
|
||||||
"Europe/London",
|
|
||||||
"Europe/Luxembourg",
|
|
||||||
"Europe/Madrid",
|
|
||||||
"Europe/Malta",
|
|
||||||
"Europe/Mariehamn",
|
|
||||||
"Europe/Minsk",
|
|
||||||
"Europe/Monaco",
|
|
||||||
"Europe/Moscow",
|
|
||||||
"Europe/Oslo",
|
|
||||||
"Europe/Paris",
|
|
||||||
"Europe/Podgorica",
|
|
||||||
"Europe/Prague",
|
|
||||||
"Europe/Riga",
|
|
||||||
"Europe/Rome",
|
|
||||||
"Europe/Samara",
|
|
||||||
"Europe/San_Marino",
|
|
||||||
"Europe/Sarajevo",
|
|
||||||
"Europe/Saratov",
|
|
||||||
"Europe/Simferopol",
|
|
||||||
"Europe/Skopje",
|
|
||||||
"Europe/Sofia",
|
|
||||||
"Europe/Stockholm",
|
|
||||||
"Europe/Tallinn",
|
|
||||||
"Europe/Tirane",
|
|
||||||
"Europe/Ulyanovsk",
|
|
||||||
"Europe/Vaduz",
|
|
||||||
"Europe/Vatican",
|
|
||||||
"Europe/Vienna",
|
|
||||||
"Europe/Vilnius",
|
|
||||||
"Europe/Volgograd",
|
|
||||||
"Europe/Warsaw",
|
|
||||||
"Europe/Zagreb",
|
|
||||||
"Europe/Zurich",
|
|
||||||
"Indian/Antananarivo",
|
|
||||||
"Indian/Chagos",
|
|
||||||
"Indian/Christmas",
|
|
||||||
"Indian/Cocos",
|
|
||||||
"Indian/Comoro",
|
|
||||||
"Indian/Kerguelen",
|
|
||||||
"Indian/Mahe",
|
|
||||||
"Indian/Maldives",
|
|
||||||
"Indian/Mauritius",
|
|
||||||
"Indian/Mayotte",
|
|
||||||
"Indian/Reunion",
|
|
||||||
"Pacific/Apia",
|
|
||||||
"Pacific/Auckland",
|
|
||||||
"Pacific/Bougainville",
|
|
||||||
"Pacific/Chatham",
|
|
||||||
"Pacific/Easter",
|
|
||||||
"Pacific/Efate",
|
|
||||||
"Pacific/Enderbury",
|
|
||||||
"Pacific/Fakaofo",
|
|
||||||
"Pacific/Fiji",
|
|
||||||
"Pacific/Funafuti",
|
|
||||||
"Pacific/Galapagos",
|
|
||||||
"Pacific/Gambier",
|
|
||||||
"Pacific/Guadalcanal",
|
|
||||||
"Pacific/Guam",
|
|
||||||
"Pacific/Honolulu",
|
|
||||||
"Pacific/Kiritimati",
|
|
||||||
"Pacific/Kosrae",
|
|
||||||
"Pacific/Kwajalein",
|
|
||||||
"Pacific/Majuro",
|
|
||||||
"Pacific/Marquesas",
|
|
||||||
"Pacific/Midway",
|
|
||||||
"Pacific/Nauru",
|
|
||||||
"Pacific/Niue",
|
|
||||||
"Pacific/Norfolk",
|
|
||||||
"Pacific/Noumea",
|
|
||||||
"Pacific/Pago_Pago",
|
|
||||||
"Pacific/Palau",
|
|
||||||
"Pacific/Pitcairn",
|
|
||||||
"Pacific/Ponape",
|
|
||||||
"Pacific/Port_Moresby",
|
|
||||||
"Pacific/Rarotonga",
|
|
||||||
"Pacific/Saipan",
|
|
||||||
"Pacific/Tahiti",
|
|
||||||
"Pacific/Tarawa",
|
|
||||||
"Pacific/Tongatapu",
|
|
||||||
"Pacific/Truk",
|
|
||||||
"Pacific/Wake",
|
|
||||||
"Pacific/Wallis"
|
|
||||||
]
|
|
||||||
|
|
||||||
LODGING_TYPES = [
|
LODGING_TYPES = [
|
||||||
('hotel', 'Hotel'),
|
('hotel', 'Hotel'),
|
||||||
('hostel', 'Hostel'),
|
('hostel', 'Hostel'),
|
||||||
|
@ -533,13 +114,13 @@ TRANSPORTATION_TYPES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# Assuming you have a default user ID you want to use
|
# Assuming you have a default user ID you want to use
|
||||||
default_user_id = 1 # Replace with an actual user ID
|
default_user = 1 # Replace with an actual user ID
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
class Visit(models.Model):
|
class Visit(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
adventure = models.ForeignKey('Adventure', on_delete=models.CASCADE, related_name='visits')
|
location = models.ForeignKey('Location', on_delete=models.CASCADE, related_name='visits')
|
||||||
start_date = models.DateTimeField(null=True, blank=True)
|
start_date = models.DateTimeField(null=True, blank=True)
|
||||||
end_date = models.DateTimeField(null=True, blank=True)
|
end_date = models.DateTimeField(null=True, blank=True)
|
||||||
timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True)
|
timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True)
|
||||||
|
@ -552,13 +133,13 @@ class Visit(models.Model):
|
||||||
raise ValidationError('The start date must be before or equal to the end date.')
|
raise ValidationError('The start date must be before or equal to the end date.')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.adventure.name} - {self.start_date} to {self.end_date}"
|
return f"{self.location.name} - {self.start_date} to {self.end_date}"
|
||||||
|
|
||||||
class Adventure(models.Model):
|
class Location(models.Model):
|
||||||
#id = models.AutoField(primary_key=True)
|
#id = models.AutoField(primary_key=True)
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
|
|
||||||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True)
|
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
|
@ -583,7 +164,7 @@ class Adventure(models.Model):
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
objects = AdventureManager()
|
objects = LocationManager()
|
||||||
|
|
||||||
def is_visited_status(self):
|
def is_visited_status(self):
|
||||||
current_date = timezone.now().date()
|
current_date = timezone.now().date()
|
||||||
|
@ -609,18 +190,18 @@ class Adventure(models.Model):
|
||||||
if self.pk: # Only check if the instance has been saved
|
if self.pk: # Only check if the instance has been saved
|
||||||
for collection in self.collections.all():
|
for collection in self.collections.all():
|
||||||
if collection.is_public and not self.is_public:
|
if collection.is_public and not self.is_public:
|
||||||
raise ValidationError(f'Adventures associated with a public collection must be public. Collection: {collection.name} Adventure: {self.name}')
|
raise ValidationError(f'Locations associated with a public collection must be public. Collection: {collection.name} Location: {self.name}')
|
||||||
|
|
||||||
# Only enforce same-user constraint for non-shared collections
|
# Only enforce same-user constraint for non-shared collections
|
||||||
if self.user_id != collection.user_id:
|
if self.user != collection.user:
|
||||||
# Check if this is a shared collection scenario
|
# Check if this is a shared collection scenario
|
||||||
# Allow if the adventure owner has access to the collection through sharing
|
# Allow if the location owner has access to the collection through sharing
|
||||||
if not collection.shared_with.filter(uuid=self.user_id.uuid).exists():
|
if not collection.shared_with.filter(uuid=self.user.uuid).exists():
|
||||||
raise ValidationError(f'Adventures must be associated with collections owned by the same user or shared collections. Collection owner: {collection.user_id.username} Adventure owner: {self.user_id.username}')
|
raise ValidationError(f'Locations must be associated with collections owned by the same user or shared collections. Collection owner: {collection.user.username} Location owner: {self.user.username}')
|
||||||
|
|
||||||
if self.category:
|
if self.category:
|
||||||
if self.user_id != self.category.user_id:
|
if self.user != self.category.user:
|
||||||
raise ValidationError(f'Adventures must be associated with categories owned by the same user. Category owner: {self.category.user_id.username} Adventure owner: {self.user_id.username}')
|
raise ValidationError(f'Locations must be associated with categories owned by the same user. Category owner: {self.category.user.username} Location owner: {self.user.username}')
|
||||||
|
|
||||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None, _skip_geocode=False, _skip_shared_validation=False):
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None, _skip_geocode=False, _skip_shared_validation=False):
|
||||||
if force_insert and force_update:
|
if force_insert and force_update:
|
||||||
|
@ -628,7 +209,7 @@ class Adventure(models.Model):
|
||||||
|
|
||||||
if not self.category:
|
if not self.category:
|
||||||
category, _ = Category.objects.get_or_create(
|
category, _ = Category.objects.get_or_create(
|
||||||
user_id=self.user_id,
|
user=self.user,
|
||||||
name='general',
|
name='general',
|
||||||
defaults={'display_name': 'General', 'icon': '🌍'}
|
defaults={'display_name': 'General', 'icon': '🌍'}
|
||||||
)
|
)
|
||||||
|
@ -662,8 +243,8 @@ class Adventure(models.Model):
|
||||||
class Collection(models.Model):
|
class Collection(models.Model):
|
||||||
#id = models.AutoField(primary_key=True)
|
#id = models.AutoField(primary_key=True)
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
is_public = models.BooleanField(default=False)
|
is_public = models.BooleanField(default=False)
|
||||||
|
@ -681,7 +262,7 @@ class Collection(models.Model):
|
||||||
# Updated to use the new related_name 'adventures'
|
# Updated to use the new related_name 'adventures'
|
||||||
for adventure in self.adventures.all():
|
for adventure in self.adventures.all():
|
||||||
if not adventure.is_public:
|
if not adventure.is_public:
|
||||||
raise ValidationError(f'Public collections cannot be associated with private adventures. Collection: {self.name} Adventure: {adventure.name}')
|
raise ValidationError(f'Public collections cannot be associated with private locations. Collection: {self.name} Adventure: {adventure.name}')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -689,8 +270,8 @@ class Collection(models.Model):
|
||||||
class Transportation(models.Model):
|
class Transportation(models.Model):
|
||||||
#id = models.AutoField(primary_key=True)
|
#id = models.AutoField(primary_key=True)
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES)
|
type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
|
@ -720,8 +301,8 @@ class Transportation(models.Model):
|
||||||
if self.collection:
|
if self.collection:
|
||||||
if self.collection.is_public and not self.is_public:
|
if self.collection.is_public and not self.is_public:
|
||||||
raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
|
raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
|
||||||
if self.user_id != self.collection.user_id:
|
if self.user != self.collection.user:
|
||||||
raise ValidationError('Transportations must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username)
|
raise ValidationError('Transportations must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Transportation owner: ' + self.user.username)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -729,8 +310,8 @@ class Transportation(models.Model):
|
||||||
class Note(models.Model):
|
class Note(models.Model):
|
||||||
#id = models.AutoField(primary_key=True)
|
#id = models.AutoField(primary_key=True)
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
content = models.TextField(blank=True, null=True)
|
content = models.TextField(blank=True, null=True)
|
||||||
links = ArrayField(models.URLField(), blank=True, null=True)
|
links = ArrayField(models.URLField(), blank=True, null=True)
|
||||||
|
@ -744,8 +325,8 @@ class Note(models.Model):
|
||||||
if self.collection:
|
if self.collection:
|
||||||
if self.collection.is_public and not self.is_public:
|
if self.collection.is_public and not self.is_public:
|
||||||
raise ValidationError('Notes associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
|
raise ValidationError('Notes associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
|
||||||
if self.user_id != self.collection.user_id:
|
if self.user != self.collection.user:
|
||||||
raise ValidationError('Notes must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username)
|
raise ValidationError('Notes must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Transportation owner: ' + self.user.username)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -753,8 +334,8 @@ class Note(models.Model):
|
||||||
class Checklist(models.Model):
|
class Checklist(models.Model):
|
||||||
# id = models.AutoField(primary_key=True)
|
# id = models.AutoField(primary_key=True)
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
date = models.DateField(blank=True, null=True)
|
date = models.DateField(blank=True, null=True)
|
||||||
is_public = models.BooleanField(default=False)
|
is_public = models.BooleanField(default=False)
|
||||||
|
@ -766,8 +347,8 @@ class Checklist(models.Model):
|
||||||
if self.collection:
|
if self.collection:
|
||||||
if self.collection.is_public and not self.is_public:
|
if self.collection.is_public and not self.is_public:
|
||||||
raise ValidationError('Checklists associated with a public collection must be public. Collection: ' + self.collection.name + ' Checklist: ' + self.name)
|
raise ValidationError('Checklists associated with a public collection must be public. Collection: ' + self.collection.name + ' Checklist: ' + self.name)
|
||||||
if self.user_id != self.collection.user_id:
|
if self.user != self.collection.user:
|
||||||
raise ValidationError('Checklists must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Checklist owner: ' + self.user_id.username)
|
raise ValidationError('Checklists must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Checklist owner: ' + self.user.username)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -775,8 +356,8 @@ class Checklist(models.Model):
|
||||||
class ChecklistItem(models.Model):
|
class ChecklistItem(models.Model):
|
||||||
#id = models.AutoField(primary_key=True)
|
#id = models.AutoField(primary_key=True)
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
is_checked = models.BooleanField(default=False)
|
is_checked = models.BooleanField(default=False)
|
||||||
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE)
|
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE)
|
||||||
|
@ -786,8 +367,8 @@ class ChecklistItem(models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.checklist.is_public and not self.checklist.is_public:
|
if self.checklist.is_public and not self.checklist.is_public:
|
||||||
raise ValidationError('Checklist items associated with a public checklist must be public. Checklist: ' + self.checklist.name + ' Checklist item: ' + self.name)
|
raise ValidationError('Checklist items associated with a public checklist must be public. Checklist: ' + self.checklist.name + ' Checklist item: ' + self.name)
|
||||||
if self.user_id != self.checklist.user_id:
|
if self.user != self.checklist.user:
|
||||||
raise ValidationError('Checklist items must be associated with checklists owned by the same user. Checklist owner: ' + self.checklist.user_id.username + ' Checklist item owner: ' + self.user_id.username)
|
raise ValidationError('Checklist items must be associated with checklists owned by the same user. Checklist owner: ' + self.checklist.user.username + ' Checklist item owner: ' + self.user.username)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -803,9 +384,9 @@ class PathAndRename:
|
||||||
filename = f"{uuid.uuid4()}.{ext}"
|
filename = f"{uuid.uuid4()}.{ext}"
|
||||||
return os.path.join(self.path, filename)
|
return os.path.join(self.path, filename)
|
||||||
|
|
||||||
class AdventureImage(models.Model):
|
class LocationImage(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(User, on_delete=models.CASCADE, default=default_user_id)
|
user = models.ForeignKey(User, on_delete=models.CASCADE, default=default_user)
|
||||||
image = ResizedImageField(
|
image = ResizedImageField(
|
||||||
force_format="WEBP",
|
force_format="WEBP",
|
||||||
quality=75,
|
quality=75,
|
||||||
|
@ -814,7 +395,7 @@ class AdventureImage(models.Model):
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
immich_id = models.CharField(max_length=200, null=True, blank=True)
|
immich_id = models.CharField(max_length=200, null=True, blank=True)
|
||||||
adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE)
|
location = models.ForeignKey(Location, related_name='images', on_delete=models.CASCADE)
|
||||||
is_primary = models.BooleanField(default=False)
|
is_primary = models.BooleanField(default=False)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -843,10 +424,10 @@ class AdventureImage(models.Model):
|
||||||
|
|
||||||
class Attachment(models.Model):
|
class Attachment(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
file = models.FileField(upload_to=PathAndRename('attachments/'),validators=[validate_file_extension])
|
file = models.FileField(upload_to=PathAndRename('attachments/'),validators=[validate_file_extension])
|
||||||
adventure = models.ForeignKey(Adventure, related_name='attachments', on_delete=models.CASCADE)
|
location = models.ForeignKey(Location, related_name='attachments', on_delete=models.CASCADE)
|
||||||
name = models.CharField(max_length=200, null=True, blank=True)
|
name = models.CharField(max_length=200, null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -854,15 +435,15 @@ class Attachment(models.Model):
|
||||||
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
display_name = models.CharField(max_length=200)
|
display_name = models.CharField(max_length=200)
|
||||||
icon = models.CharField(max_length=200, default='🌍')
|
icon = models.CharField(max_length=200, default='🌍')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = 'Categories'
|
verbose_name_plural = 'Categories'
|
||||||
unique_together = ['name', 'user_id']
|
unique_together = ['name', 'user']
|
||||||
|
|
||||||
def clean(self) -> None:
|
def clean(self) -> None:
|
||||||
self.name = self.name.lower().strip()
|
self.name = self.name.lower().strip()
|
||||||
|
@ -875,8 +456,8 @@ class Category(models.Model):
|
||||||
|
|
||||||
class Lodging(models.Model):
|
class Lodging(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other')
|
type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other')
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
|
@ -901,9 +482,9 @@ class Lodging(models.Model):
|
||||||
|
|
||||||
if self.collection:
|
if self.collection:
|
||||||
if self.collection.is_public and not self.is_public:
|
if self.collection.is_public and not self.is_public:
|
||||||
raise ValidationError('Lodging associated with a public collection must be public. Collection: ' + self.collection.name + ' Loging: ' + self.name)
|
raise ValidationError('Lodging associated with a public collection must be public. Collection: ' + self.collection.name + ' Lodging: ' + self.name)
|
||||||
if self.user_id != self.collection.user_id:
|
if self.user != self.collection.user:
|
||||||
raise ValidationError('Lodging must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Lodging owner: ' + self.user_id.username)
|
raise ValidationError('Lodging must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Lodging owner: ' + self.user.username)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
|
@ -7,8 +7,8 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
return True
|
return True
|
||||||
# obj.user_id is FK to User, compare with request.user
|
# obj.user is FK to User, compare with request.user
|
||||||
return obj.user_id == request.user
|
return obj.user == request.user
|
||||||
|
|
||||||
|
|
||||||
class IsPublicReadOnly(permissions.BasePermission):
|
class IsPublicReadOnly(permissions.BasePermission):
|
||||||
|
@ -17,8 +17,8 @@ class IsPublicReadOnly(permissions.BasePermission):
|
||||||
"""
|
"""
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
return obj.is_public or obj.user_id == request.user
|
return obj.is_public or obj.user == request.user
|
||||||
return obj.user_id == request.user
|
return obj.user == request.user
|
||||||
|
|
||||||
|
|
||||||
class CollectionShared(permissions.BasePermission):
|
class CollectionShared(permissions.BasePermission):
|
||||||
|
@ -48,10 +48,10 @@ class CollectionShared(permissions.BasePermission):
|
||||||
|
|
||||||
# Read permission if public or owner
|
# Read permission if public or owner
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
return obj.is_public or obj.user_id == user
|
return obj.is_public or obj.user == user
|
||||||
|
|
||||||
# Write permission only if owner or shared user via collections
|
# Write permission only if owner or shared user via collections
|
||||||
if obj.user_id == user:
|
if obj.user == user:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if hasattr(obj, 'collections'):
|
if hasattr(obj, 'collections'):
|
||||||
|
@ -76,7 +76,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
if obj.is_public:
|
if obj.is_public:
|
||||||
return True
|
return True
|
||||||
if obj.user_id == user:
|
if obj.user == user:
|
||||||
return True
|
return True
|
||||||
# If user in shared_with of any collection related to obj
|
# If user in shared_with of any collection related to obj
|
||||||
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
|
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
|
||||||
|
@ -88,7 +88,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# For write methods, allow if owner or shared user
|
# For write methods, allow if owner or shared user
|
||||||
if obj.user_id == user:
|
if obj.user == user:
|
||||||
return True
|
return True
|
||||||
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
|
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import os
|
import os
|
||||||
from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging
|
from .models import Location, LocationImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from main.utils import CustomModelSerializer
|
from main.utils import CustomModelSerializer
|
||||||
from users.serializers import CustomUserDetailsSerializer
|
from users.serializers import CustomUserDetailsSerializer
|
||||||
|
@ -11,15 +11,15 @@ from integrations.models import ImmichIntegration
|
||||||
|
|
||||||
class AdventureImageSerializer(CustomModelSerializer):
|
class AdventureImageSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdventureImage
|
model = LocationImage
|
||||||
fields = ['id', 'image', 'adventure', 'is_primary', 'user_id', 'immich_id']
|
fields = ['id', 'image', 'location', 'is_primary', 'user', 'immich_id']
|
||||||
read_only_fields = ['id', 'user_id']
|
read_only_fields = ['id', 'user']
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
# If immich_id is set, check for user integration once
|
# If immich_id is set, check for user integration once
|
||||||
integration = None
|
integration = None
|
||||||
if instance.immich_id:
|
if instance.immich_id:
|
||||||
integration = ImmichIntegration.objects.filter(user=instance.user_id).first()
|
integration = ImmichIntegration.objects.filter(user=instance.user).first()
|
||||||
if not integration:
|
if not integration:
|
||||||
return None # Skip if Immich image but no integration
|
return None # Skip if Immich image but no integration
|
||||||
|
|
||||||
|
@ -42,8 +42,8 @@ class AttachmentSerializer(CustomModelSerializer):
|
||||||
extension = serializers.SerializerMethodField()
|
extension = serializers.SerializerMethodField()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Attachment
|
model = Attachment
|
||||||
fields = ['id', 'file', 'adventure', 'extension', 'name', 'user_id']
|
fields = ['id', 'file', 'location', 'extension', 'name', 'user']
|
||||||
read_only_fields = ['id', 'user_id']
|
read_only_fields = ['id', 'user']
|
||||||
|
|
||||||
def get_extension(self, obj):
|
def get_extension(self, obj):
|
||||||
return obj.file.name.split('.')[-1]
|
return obj.file.name.split('.')[-1]
|
||||||
|
@ -71,7 +71,7 @@ class CategorySerializer(serializers.ModelSerializer):
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
user = self.context['request'].user
|
user = self.context['request'].user
|
||||||
validated_data['name'] = validated_data['name'].lower()
|
validated_data['name'] = validated_data['name'].lower()
|
||||||
return Category.objects.create(user_id=user, **validated_data)
|
return Category.objects.create(user=user, **validated_data)
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
for attr, value in validated_data.items():
|
for attr, value in validated_data.items():
|
||||||
|
@ -82,7 +82,7 @@ class CategorySerializer(serializers.ModelSerializer):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def get_num_adventures(self, obj):
|
def get_num_adventures(self, obj):
|
||||||
return Adventure.objects.filter(category=obj, user_id=obj.user_id).count()
|
return Location.objects.filter(category=obj, user=obj.user).count()
|
||||||
|
|
||||||
class VisitSerializer(serializers.ModelSerializer):
|
class VisitSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
@ -108,13 +108,13 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Adventure
|
model = Location
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location',
|
'id', 'name', 'description', 'rating', 'activity_types', 'location',
|
||||||
'is_public', 'collections', 'created_at', 'updated_at', 'images', 'link', 'longitude',
|
'is_public', 'collections', 'created_at', 'updated_at', 'images', 'link', 'longitude',
|
||||||
'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user', 'city', 'country', 'region'
|
'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user', 'city', 'country', 'region'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'is_visited', 'user']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'is_visited']
|
||||||
|
|
||||||
def get_images(self, obj):
|
def get_images(self, obj):
|
||||||
serializer = AdventureImageSerializer(obj.images.all(), many=True, context=self.context)
|
serializer = AdventureImageSerializer(obj.images.all(), many=True, context=self.context)
|
||||||
|
@ -128,7 +128,7 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
user = self.context['request'].user
|
user = self.context['request'].user
|
||||||
for collection in collections:
|
for collection in collections:
|
||||||
if collection.user_id != user:
|
if collection.user != user:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
f"Collection '{collection.name}' does not belong to the current user."
|
f"Collection '{collection.name}' does not belong to the current user."
|
||||||
)
|
)
|
||||||
|
@ -140,7 +140,7 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
if category_data:
|
if category_data:
|
||||||
user = self.context['request'].user
|
user = self.context['request'].user
|
||||||
name = category_data.get('name', '').lower()
|
name = category_data.get('name', '').lower()
|
||||||
existing_category = Category.objects.filter(user_id=user, name=name).first()
|
existing_category = Category.objects.filter(user=user, name=name).first()
|
||||||
if existing_category:
|
if existing_category:
|
||||||
return existing_category
|
return existing_category
|
||||||
category_data['name'] = name
|
category_data['name'] = name
|
||||||
|
@ -162,7 +162,7 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
icon = category_data.icon
|
icon = category_data.icon
|
||||||
|
|
||||||
category, created = Category.objects.get_or_create(
|
category, created = Category.objects.get_or_create(
|
||||||
user_id=user,
|
user=user,
|
||||||
name=name,
|
name=name,
|
||||||
defaults={
|
defaults={
|
||||||
'display_name': display_name,
|
'display_name': display_name,
|
||||||
|
@ -172,7 +172,7 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
return category
|
return category
|
||||||
|
|
||||||
def get_user(self, obj):
|
def get_user(self, obj):
|
||||||
user = obj.user_id
|
user = obj.user
|
||||||
return CustomUserDetailsSerializer(user).data
|
return CustomUserDetailsSerializer(user).data
|
||||||
|
|
||||||
def get_is_visited(self, obj):
|
def get_is_visited(self, obj):
|
||||||
|
@ -184,11 +184,11 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
collections_data = validated_data.pop('collections', [])
|
collections_data = validated_data.pop('collections', [])
|
||||||
|
|
||||||
print(category_data)
|
print(category_data)
|
||||||
adventure = Adventure.objects.create(**validated_data)
|
adventure = Location.objects.create(**validated_data)
|
||||||
|
|
||||||
# Handle visits
|
# Handle visits
|
||||||
for visit_data in visits_data:
|
for visit_data in visits_data:
|
||||||
Visit.objects.create(adventure=adventure, **visit_data)
|
Visit.objects.create(location=adventure, **visit_data)
|
||||||
|
|
||||||
# Handle category
|
# Handle category
|
||||||
if category_data:
|
if category_data:
|
||||||
|
@ -216,7 +216,7 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
# Handle category - ONLY allow the adventure owner to change categories
|
# Handle category - ONLY allow the adventure owner to change categories
|
||||||
user = self.context['request'].user
|
user = self.context['request'].user
|
||||||
if category_data and instance.user_id == user:
|
if category_data and instance.user == user:
|
||||||
# Only the owner can set categories
|
# Only the owner can set categories
|
||||||
category = self.get_or_create_category(category_data)
|
category = self.get_or_create_category(category_data)
|
||||||
instance.category = category
|
instance.category = category
|
||||||
|
@ -241,7 +241,7 @@ class AdventureSerializer(CustomModelSerializer):
|
||||||
visit.save()
|
visit.save()
|
||||||
updated_visit_ids.add(visit_id)
|
updated_visit_ids.add(visit_id)
|
||||||
else:
|
else:
|
||||||
new_visit = Visit.objects.create(adventure=instance, **visit_data)
|
new_visit = Visit.objects.create(location=instance, **visit_data)
|
||||||
updated_visit_ids.add(new_visit.id)
|
updated_visit_ids.add(new_visit.id)
|
||||||
|
|
||||||
visits_to_delete = current_visit_ids - updated_visit_ids
|
visits_to_delete = current_visit_ids - updated_visit_ids
|
||||||
|
@ -258,13 +258,13 @@ class TransportationSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transportation
|
model = Transportation
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user_id', 'type', 'name', 'description', 'rating',
|
'id', 'user', 'type', 'name', 'description', 'rating',
|
||||||
'link', 'date', 'flight_number', 'from_location', 'to_location',
|
'link', 'date', 'flight_number', 'from_location', 'to_location',
|
||||||
'is_public', 'collection', 'created_at', 'updated_at', 'end_date',
|
'is_public', 'collection', 'created_at', 'updated_at', 'end_date',
|
||||||
'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude',
|
'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude',
|
||||||
'start_timezone', 'end_timezone', 'distance' # ✅ Add distance here
|
'start_timezone', 'end_timezone', 'distance'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'distance']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'distance']
|
||||||
|
|
||||||
def get_distance(self, obj):
|
def get_distance(self, obj):
|
||||||
if (
|
if (
|
||||||
|
@ -284,29 +284,29 @@ class LodgingSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Lodging
|
model = Lodging
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user_id', 'name', 'description', 'rating', 'link', 'check_in', 'check_out',
|
'id', 'user', 'name', 'description', 'rating', 'link', 'check_in', 'check_out',
|
||||||
'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public',
|
'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public',
|
||||||
'collection', 'created_at', 'updated_at', 'type', 'timezone'
|
'collection', 'created_at', 'updated_at', 'type', 'timezone'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user']
|
||||||
|
|
||||||
class NoteSerializer(CustomModelSerializer):
|
class NoteSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Note
|
model = Note
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user_id', 'name', 'content', 'date', 'links',
|
'id', 'user', 'name', 'content', 'date', 'links',
|
||||||
'is_public', 'collection', 'created_at', 'updated_at'
|
'is_public', 'collection', 'created_at', 'updated_at'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user']
|
||||||
|
|
||||||
class ChecklistItemSerializer(CustomModelSerializer):
|
class ChecklistItemSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ChecklistItem
|
model = ChecklistItem
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user_id', 'name', 'is_checked', 'checklist', 'created_at', 'updated_at'
|
'id', 'user', 'name', 'is_checked', 'checklist', 'created_at', 'updated_at'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'checklist']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'checklist']
|
||||||
|
|
||||||
class ChecklistSerializer(CustomModelSerializer):
|
class ChecklistSerializer(CustomModelSerializer):
|
||||||
items = ChecklistItemSerializer(many=True, source='checklistitem_set')
|
items = ChecklistItemSerializer(many=True, source='checklistitem_set')
|
||||||
|
@ -314,21 +314,21 @@ class ChecklistSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Checklist
|
model = Checklist
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user_id', 'name', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'items'
|
'id', 'user', 'name', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'items'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user']
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
items_data = validated_data.pop('checklistitem_set')
|
items_data = validated_data.pop('checklistitem_set')
|
||||||
checklist = Checklist.objects.create(**validated_data)
|
checklist = Checklist.objects.create(**validated_data)
|
||||||
|
|
||||||
for item_data in items_data:
|
for item_data in items_data:
|
||||||
# Remove user_id from item_data to avoid constraint issues
|
# Remove user from item_data to avoid constraint issues
|
||||||
item_data.pop('user_id', None)
|
item_data.pop('user', None)
|
||||||
# Set user_id from the parent checklist
|
# Set user from the parent checklist
|
||||||
ChecklistItem.objects.create(
|
ChecklistItem.objects.create(
|
||||||
checklist=checklist,
|
checklist=checklist,
|
||||||
user_id=checklist.user_id,
|
user=checklist.user,
|
||||||
**item_data
|
**item_data
|
||||||
)
|
)
|
||||||
return checklist
|
return checklist
|
||||||
|
@ -348,8 +348,8 @@ class ChecklistSerializer(CustomModelSerializer):
|
||||||
# Update or create items
|
# Update or create items
|
||||||
updated_item_ids = set()
|
updated_item_ids = set()
|
||||||
for item_data in items_data:
|
for item_data in items_data:
|
||||||
# Remove user_id from item_data to avoid constraint issues
|
# Remove user from item_data to avoid constraint issues
|
||||||
item_data.pop('user_id', None)
|
item_data.pop('user', None)
|
||||||
|
|
||||||
item_id = item_data.get('id')
|
item_id = item_data.get('id')
|
||||||
if item_id:
|
if item_id:
|
||||||
|
@ -363,14 +363,14 @@ class ChecklistSerializer(CustomModelSerializer):
|
||||||
# If ID is provided but doesn't exist, create new item
|
# If ID is provided but doesn't exist, create new item
|
||||||
ChecklistItem.objects.create(
|
ChecklistItem.objects.create(
|
||||||
checklist=instance,
|
checklist=instance,
|
||||||
user_id=instance.user_id,
|
user=instance.user,
|
||||||
**item_data
|
**item_data
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If no ID is provided, create new item
|
# If no ID is provided, create new item
|
||||||
ChecklistItem.objects.create(
|
ChecklistItem.objects.create(
|
||||||
checklist=instance,
|
checklist=instance,
|
||||||
user_id=instance.user_id,
|
user=instance.user,
|
||||||
**item_data
|
**item_data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -399,8 +399,8 @@ class CollectionSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Collection
|
model = Collection
|
||||||
fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'lodging']
|
fields = ['id', 'description', 'user', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'lodging']
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
read_only_fields = ['id', 'created_at', 'updated_at', 'user']
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
representation = super().to_representation(instance)
|
representation = super().to_representation(instance)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from django.db.models.signals import m2m_changed
|
from django.db.models.signals import m2m_changed
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from adventures.models import Adventure
|
from adventures.models import Location
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Adventure.collections.through)
|
@receiver(m2m_changed, sender=Location.collections.through)
|
||||||
def update_adventure_publicity(sender, instance, action, **kwargs):
|
def update_adventure_publicity(sender, instance, action, **kwargs):
|
||||||
"""
|
"""
|
||||||
Signal handler to update adventure publicity when collections are added/removed
|
Signal handler to update adventure publicity when collections are added/removed
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from adventures.models import AdventureImage, Attachment
|
from adventures.models import LocationImage, Attachment
|
||||||
|
|
||||||
protected_paths = ['images/', 'attachments/']
|
protected_paths = ['images/', 'attachments/']
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@ def checkFilePermission(fileId, user, mediaType):
|
||||||
# Construct the full relative path to match the database field
|
# Construct the full relative path to match the database field
|
||||||
image_path = f"images/{fileId}"
|
image_path = f"images/{fileId}"
|
||||||
# Fetch the AdventureImage object
|
# Fetch the AdventureImage object
|
||||||
adventure = AdventureImage.objects.get(image=image_path).adventure
|
adventure = LocationImage.objects.get(image=image_path).location
|
||||||
if adventure.is_public:
|
if adventure.is_public:
|
||||||
return True
|
return True
|
||||||
elif adventure.user_id == user:
|
elif adventure.user == user:
|
||||||
return True
|
return True
|
||||||
elif adventure.collections.exists():
|
elif adventure.collections.exists():
|
||||||
# Check if the user is in any collection's shared_with list
|
# Check if the user is in any collection's shared_with list
|
||||||
|
@ -23,7 +23,7 @@ def checkFilePermission(fileId, user, mediaType):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
except AdventureImage.DoesNotExist:
|
except LocationImage.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
elif mediaType == 'attachments/':
|
elif mediaType == 'attachments/':
|
||||||
try:
|
try:
|
||||||
|
@ -31,10 +31,10 @@ def checkFilePermission(fileId, user, mediaType):
|
||||||
attachment_path = f"attachments/{fileId}"
|
attachment_path = f"attachments/{fileId}"
|
||||||
# Fetch the Attachment object
|
# Fetch the Attachment object
|
||||||
attachment = Attachment.objects.get(file=attachment_path)
|
attachment = Attachment.objects.get(file=attachment_path)
|
||||||
adventure = attachment.adventure
|
adventure = attachment.location
|
||||||
if adventure.is_public:
|
if adventure.is_public:
|
||||||
return True
|
return True
|
||||||
elif adventure.user_id == user:
|
elif adventure.user == user:
|
||||||
return True
|
return True
|
||||||
elif adventure.collections.exists():
|
elif adventure.collections.exists():
|
||||||
# Check if the user is in any collection's shared_with list
|
# Check if the user is in any collection's shared_with list
|
||||||
|
|
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.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from adventures.models import Adventure
|
from adventures.models import Location
|
||||||
|
|
||||||
class ActivityTypesView(viewsets.ViewSet):
|
class ActivityTypesView(viewsets.ViewSet):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
@ -18,7 +18,7 @@ class ActivityTypesView(viewsets.ViewSet):
|
||||||
Returns:
|
Returns:
|
||||||
Response: A response containing a list of distinct activity types.
|
Response: A response containing a list of distinct activity types.
|
||||||
"""
|
"""
|
||||||
types = Adventure.objects.filter(user_id=request.user.id).values_list('activity_types', flat=True).distinct()
|
types = Location.objects.filter(user=request.user.id).values_list('activity_types', flat=True).distinct()
|
||||||
|
|
||||||
allTypes = []
|
allTypes = []
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from adventures.models import Adventure, AdventureImage
|
from adventures.models import Location, LocationImage
|
||||||
from adventures.serializers import AdventureImageSerializer
|
from adventures.serializers import AdventureImageSerializer
|
||||||
from integrations.models import ImmichIntegration
|
from integrations.models import ImmichIntegration
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -26,7 +26,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
adventure = instance.adventure
|
adventure = instance.adventure
|
||||||
if adventure.user_id != request.user:
|
if adventure.user != request.user:
|
||||||
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
# Check if the image is already the primary image
|
# Check if the image is already the primary image
|
||||||
|
@ -34,7 +34,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Set the current primary image to false
|
# Set the current primary image to false
|
||||||
AdventureImage.objects.filter(adventure=adventure, is_primary=True).update(is_primary=False)
|
LocationImage.objects.filter(adventure=adventure, is_primary=True).update(is_primary=False)
|
||||||
|
|
||||||
# Set the new image to true
|
# Set the new image to true
|
||||||
instance.is_primary = True
|
instance.is_primary = True
|
||||||
|
@ -46,11 +46,11 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
|
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
adventure_id = request.data.get('adventure')
|
adventure_id = request.data.get('adventure')
|
||||||
try:
|
try:
|
||||||
adventure = Adventure.objects.get(id=adventure_id)
|
adventure = Location.objects.get(id=adventure_id)
|
||||||
except Adventure.DoesNotExist:
|
except Location.DoesNotExist:
|
||||||
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if adventure.user_id != request.user:
|
if adventure.user != request.user:
|
||||||
# Check if the adventure has any collections
|
# Check if the adventure has any collections
|
||||||
if adventure.collections.exists():
|
if adventure.collections.exists():
|
||||||
# Check if the user is in the shared_with list of any of the adventure's collections
|
# Check if the user is in the shared_with list of any of the adventure's collections
|
||||||
|
@ -66,7 +66,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
# Handle Immich ID for shared users by downloading the image
|
# Handle Immich ID for shared users by downloading the image
|
||||||
if (request.user != adventure.user_id and
|
if (request.user != adventure.user and
|
||||||
'immich_id' in request.data and
|
'immich_id' in request.data and
|
||||||
request.data.get('immich_id')):
|
request.data.get('immich_id')):
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
# Get the shared user's Immich integration
|
# Get the shared user's Immich integration
|
||||||
try:
|
try:
|
||||||
user_integration = ImmichIntegration.objects.get(user_id=request.user)
|
user_integration = ImmichIntegration.objects.get(user=request.user)
|
||||||
except ImmichIntegration.DoesNotExist:
|
except ImmichIntegration.DoesNotExist:
|
||||||
return Response({
|
return Response({
|
||||||
"error": "No Immich integration found for your account. Please set up Immich integration first.",
|
"error": "No Immich integration found for your account. Please set up Immich integration first.",
|
||||||
|
@ -122,7 +122,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
# Save with the downloaded image
|
# Save with the downloaded image
|
||||||
adventure = serializer.validated_data['adventure']
|
adventure = serializer.validated_data['adventure']
|
||||||
serializer.save(user_id=adventure.user_id, image=image_file)
|
serializer.save(user=adventure.user, image=image_file)
|
||||||
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
@ -145,11 +145,11 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
adventure_id = request.data.get('adventure')
|
adventure_id = request.data.get('adventure')
|
||||||
try:
|
try:
|
||||||
adventure = Adventure.objects.get(id=adventure_id)
|
adventure = Location.objects.get(id=adventure_id)
|
||||||
except Adventure.DoesNotExist:
|
except Location.DoesNotExist:
|
||||||
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if adventure.user_id != request.user:
|
if adventure.user != request.user:
|
||||||
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
return super().update(request, *args, **kwargs)
|
return super().update(request, *args, **kwargs)
|
||||||
|
@ -165,7 +165,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
adventure = instance.adventure
|
adventure = instance.adventure
|
||||||
if adventure.user_id != request.user:
|
if adventure.user != request.user:
|
||||||
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
return super().destroy(request, *args, **kwargs)
|
return super().destroy(request, *args, **kwargs)
|
||||||
|
@ -176,7 +176,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
adventure = instance.adventure
|
adventure = instance.adventure
|
||||||
if adventure.user_id != request.user:
|
if adventure.user != request.user:
|
||||||
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
return super().partial_update(request, *args, **kwargs)
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
@ -192,9 +192,9 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Updated queryset to include images from adventures the user owns OR has shared access to
|
# Updated queryset to include images from adventures the user owns OR has shared access to
|
||||||
queryset = AdventureImage.objects.filter(
|
queryset = LocationImage.objects.filter(
|
||||||
Q(adventure__id=adventure_uuid) & (
|
Q(adventure__id=adventure_uuid) & (
|
||||||
Q(adventure__user_id=request.user) | # User owns the adventure
|
Q(adventure__user=request.user) | # User owns the adventure
|
||||||
Q(adventure__collections__shared_with=request.user) # User has shared access via collection
|
Q(adventure__collections__shared_with=request.user) # User has shared access via collection
|
||||||
)
|
)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
@ -204,12 +204,12 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Updated to include images from adventures the user owns OR has shared access to
|
# Updated to include images from adventures the user owns OR has shared access to
|
||||||
return AdventureImage.objects.filter(
|
return LocationImage.objects.filter(
|
||||||
Q(adventure__user_id=self.request.user) | # User owns the adventure
|
Q(adventure__user=self.request.user) | # User owns the adventure
|
||||||
Q(adventure__collections__shared_with=self.request.user) # User has shared access via collection
|
Q(adventure__collections__shared_with=self.request.user) # User has shared access via collection
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
# Always set the image owner to the adventure owner, not the current user
|
# Always set the image owner to the adventure owner, not the current user
|
||||||
adventure = serializer.validated_data['adventure']
|
adventure = serializer.validated_data['adventure']
|
||||||
serializer.save(user_id=adventure.user_id)
|
serializer.save(user=adventure.user)
|
|
@ -8,7 +8,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from adventures.models import Adventure, Category, Transportation, Lodging
|
from adventures.models import Location, Category, Transportation, Lodging
|
||||||
from adventures.permissions import IsOwnerOrSharedWithFullAccess
|
from adventures.permissions import IsOwnerOrSharedWithFullAccess
|
||||||
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
|
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
|
||||||
from adventures.utils import pagination
|
from adventures.utils import pagination
|
||||||
|
@ -35,13 +35,13 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
if self.action in public_allowed_actions:
|
if self.action in public_allowed_actions:
|
||||||
return Adventure.objects.retrieve_adventures(
|
return Location.objects.retrieve_adventures(
|
||||||
user, include_public=True
|
user, include_public=True
|
||||||
).order_by('-updated_at')
|
).order_by('-updated_at')
|
||||||
return Adventure.objects.none()
|
return Location.objects.none()
|
||||||
|
|
||||||
include_public = self.action in public_allowed_actions
|
include_public = self.action in public_allowed_actions
|
||||||
return Adventure.objects.retrieve_adventures(
|
return Location.objects.retrieve_adventures(
|
||||||
user,
|
user,
|
||||||
include_public=include_public,
|
include_public=include_public,
|
||||||
include_owned=True,
|
include_owned=True,
|
||||||
|
@ -116,7 +116,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
# Use the current user as owner since ManyToMany allows multiple collection owners
|
# Use the current user as owner since ManyToMany allows multiple collection owners
|
||||||
user_to_assign = self.request.user
|
user_to_assign = self.request.user
|
||||||
|
|
||||||
serializer.save(user_id=user_to_assign)
|
serializer.save(user=user_to_assign)
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
"""Update adventure."""
|
"""Update adventure."""
|
||||||
|
@ -153,12 +153,12 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
# Handle 'all' types
|
# Handle 'all' types
|
||||||
if 'all' in types:
|
if 'all' in types:
|
||||||
types = Category.objects.filter(
|
types = Category.objects.filter(
|
||||||
user_id=request.user
|
user=request.user
|
||||||
).values_list('name', flat=True)
|
).values_list('name', flat=True)
|
||||||
else:
|
else:
|
||||||
# Validate provided types
|
# Validate provided types
|
||||||
if not types or not all(
|
if not types or not all(
|
||||||
Category.objects.filter(user_id=request.user, name=type_name).exists()
|
Category.objects.filter(user=request.user, name=type_name).exists()
|
||||||
for type_name in types
|
for type_name in types
|
||||||
):
|
):
|
||||||
return Response(
|
return Response(
|
||||||
|
@ -167,9 +167,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build base queryset
|
# Build base queryset
|
||||||
queryset = Adventure.objects.filter(
|
queryset = Location.objects.filter(
|
||||||
category__in=Category.objects.filter(name__in=types, user_id=request.user),
|
category__in=Category.objects.filter(name__in=types, user=request.user),
|
||||||
user_id=request.user.id
|
user=request.user.id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply visit status filtering
|
# Apply visit status filtering
|
||||||
|
@ -187,12 +187,12 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
include_collections = request.query_params.get('include_collections', 'false') == 'true'
|
include_collections = request.query_params.get('include_collections', 'false') == 'true'
|
||||||
|
|
||||||
# Build queryset with collection filtering
|
# Build queryset with collection filtering
|
||||||
base_filter = Q(user_id=request.user.id)
|
base_filter = Q(user=request.user.id)
|
||||||
|
|
||||||
if include_collections:
|
if include_collections:
|
||||||
queryset = Adventure.objects.filter(base_filter)
|
queryset = Location.objects.filter(base_filter)
|
||||||
else:
|
else:
|
||||||
queryset = Adventure.objects.filter(base_filter, collections__isnull=True)
|
queryset = Location.objects.filter(base_filter, collections__isnull=True)
|
||||||
|
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
@ -225,7 +225,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
def _validate_collection_permissions(self, collections):
|
def _validate_collection_permissions(self, collections):
|
||||||
"""Validate user has permission to use all provided collections. Only the owner or shared users can use collections."""
|
"""Validate user has permission to use all provided collections. Only the owner or shared users can use collections."""
|
||||||
for collection in collections:
|
for collection in collections:
|
||||||
if not (collection.user_id == self.request.user or
|
if not (collection.user == self.request.user or
|
||||||
collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
|
collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
f"You do not have permission to use collection '{collection.name}'."
|
f"You do not have permission to use collection '{collection.name}'."
|
||||||
|
@ -235,7 +235,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
"""Validate permissions for collection updates (add/remove)."""
|
"""Validate permissions for collection updates (add/remove)."""
|
||||||
# Check permissions for new collections being added
|
# Check permissions for new collections being added
|
||||||
for collection in new_collections:
|
for collection in new_collections:
|
||||||
if (collection.user_id != self.request.user and
|
if (collection.user != self.request.user and
|
||||||
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
|
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
f"You do not have permission to use collection '{collection.name}'."
|
f"You do not have permission to use collection '{collection.name}'."
|
||||||
|
@ -247,7 +247,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
collections_to_remove = current_collections - new_collections_set
|
collections_to_remove = current_collections - new_collections_set
|
||||||
|
|
||||||
for collection in collections_to_remove:
|
for collection in collections_to_remove:
|
||||||
if (collection.user_id != self.request.user and
|
if (collection.user != self.request.user and
|
||||||
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
|
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
f"You cannot remove the adventure from collection '{collection.name}' "
|
f"You cannot remove the adventure from collection '{collection.name}' "
|
||||||
|
@ -284,7 +284,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check ownership
|
# Check ownership
|
||||||
if user.is_authenticated and adventure.user_id == user:
|
if user.is_authenticated and adventure.user == user:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check shared collection access
|
# Check shared collection access
|
||||||
|
|
|
@ -2,7 +2,7 @@ from rest_framework import viewsets, status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from adventures.models import Adventure, Attachment
|
from adventures.models import Location, Attachment
|
||||||
from adventures.serializers import AttachmentSerializer
|
from adventures.serializers import AttachmentSerializer
|
||||||
|
|
||||||
class AttachmentViewSet(viewsets.ModelViewSet):
|
class AttachmentViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -10,7 +10,7 @@ class AttachmentViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Attachment.objects.filter(user_id=self.request.user)
|
return Attachment.objects.filter(user=self.request.user)
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'])
|
||||||
def attachment_delete(self, request, *args, **kwargs):
|
def attachment_delete(self, request, *args, **kwargs):
|
||||||
|
@ -19,38 +19,38 @@ class AttachmentViewSet(viewsets.ModelViewSet):
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
|
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
adventure_id = request.data.get('adventure')
|
location_id = request.data.get('location')
|
||||||
try:
|
try:
|
||||||
adventure = Adventure.objects.get(id=adventure_id)
|
location = Location.objects.get(id=location_id)
|
||||||
except Adventure.DoesNotExist:
|
except Location.DoesNotExist:
|
||||||
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"error": "Location not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if adventure.user_id != request.user:
|
if location.user != request.user:
|
||||||
# Check if the adventure has any collections
|
# Check if the location has any collections
|
||||||
if adventure.collections.exists():
|
if location.collections.exists():
|
||||||
# Check if the user is in the shared_with list of any of the adventure's collections
|
# Check if the user is in the shared_with list of any of the location's collections
|
||||||
user_has_access = False
|
user_has_access = False
|
||||||
for collection in adventure.collections.all():
|
for collection in location.collections.all():
|
||||||
if collection.shared_with.filter(id=request.user.id).exists():
|
if collection.shared_with.filter(id=request.user.id).exists():
|
||||||
user_has_access = True
|
user_has_access = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if not user_has_access:
|
if not user_has_access:
|
||||||
return Response({"error": "User does not have permission to access this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"error": "User does not have permission to access this location"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
else:
|
else:
|
||||||
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
return super().create(request, *args, **kwargs)
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
adventure_id = self.request.data.get('adventure')
|
location_id = self.request.data.get('location')
|
||||||
adventure = Adventure.objects.get(id=adventure_id)
|
location = Location.objects.get(id=location_id)
|
||||||
|
|
||||||
# If the adventure belongs to collections, set the owner to the collection owner
|
# If the location belongs to collections, set the owner to the collection owner
|
||||||
if adventure.collections.exists():
|
if location.collections.exists():
|
||||||
# Get the first collection's owner (assuming all collections have the same owner)
|
# Get the first collection's owner (assuming all collections have the same owner)
|
||||||
collection = adventure.collections.first()
|
collection = location.collections.first()
|
||||||
serializer.save(user_id=collection.user_id)
|
serializer.save(user=collection.user)
|
||||||
else:
|
else:
|
||||||
# Otherwise, set the owner to the request user
|
# Otherwise, set the owner to the request user
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
|
@ -2,7 +2,7 @@ from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from adventures.models import Category, Adventure
|
from adventures.models import Category, Location
|
||||||
from adventures.serializers import CategorySerializer
|
from adventures.serializers import CategorySerializer
|
||||||
|
|
||||||
class CategoryViewSet(viewsets.ModelViewSet):
|
class CategoryViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -11,7 +11,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Category.objects.filter(user_id=self.request.user)
|
return Category.objects.filter(user=self.request.user)
|
||||||
|
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def categories(self, request):
|
def categories(self, request):
|
||||||
|
@ -24,7 +24,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
if instance.user_id != request.user:
|
if instance.user != request.user:
|
||||||
return Response({"error": "User does not own this category"}, status
|
return Response({"error": "User does not own this category"}, status
|
||||||
=400)
|
=400)
|
||||||
|
|
||||||
|
@ -32,11 +32,11 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
||||||
return Response({"error": "Cannot delete the general category"}, status=400)
|
return Response({"error": "Cannot delete the general category"}, status=400)
|
||||||
|
|
||||||
# set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user
|
# set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user
|
||||||
general_category = Category.objects.filter(user_id=request.user, name='general').first()
|
general_category = Category.objects.filter(user=request.user, name='general').first()
|
||||||
|
|
||||||
if not general_category:
|
if not general_category:
|
||||||
general_category = Category.objects.create(user_id=request.user, name='general', icon='🌍', display_name='General')
|
general_category = Category.objects.create(user=request.user, name='general', icon='🌍', display_name='General')
|
||||||
|
|
||||||
Adventure.objects.filter(category=instance).update(category=general_category)
|
Location.objects.filter(category=instance).update(category=general_category)
|
||||||
|
|
||||||
return super().destroy(request, *args, **kwargs)
|
return super().destroy(request, *args, **kwargs)
|
|
@ -24,7 +24,7 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response({"error": "User is not authenticated"}, status=400)
|
return Response({"error": "User is not authenticated"}, status=400)
|
||||||
queryset = Checklist.objects.filter(
|
queryset = Checklist.objects.filter(
|
||||||
Q(user_id=request.user.id)
|
Q(user=request.user.id)
|
||||||
)
|
)
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
@ -41,12 +41,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
||||||
if self.action == 'retrieve':
|
if self.action == 'retrieve':
|
||||||
# For individual adventure retrieval, include public adventures
|
# For individual adventure retrieval, include public adventures
|
||||||
return Checklist.objects.filter(
|
return Checklist.objects.filter(
|
||||||
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
else:
|
else:
|
||||||
# For other actions, include user's own adventures and shared adventures
|
# For other actions, include user's own adventures and shared adventures
|
||||||
return Checklist.objects.filter(
|
return Checklist.objects.filter(
|
||||||
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
|
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
@ -65,11 +65,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
if new_collection is not None and new_collection!=instance.collection:
|
if new_collection is not None and new_collection!=instance.collection:
|
||||||
# Check if the user is the owner of the new collection
|
# Check if the user is the owner of the new collection
|
||||||
if new_collection.user_id != user or instance.user_id != user:
|
if new_collection.user != user or instance.user != user:
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
elif new_collection is None:
|
elif new_collection is None:
|
||||||
# Handle the case where the user is trying to set the collection to None
|
# Handle the case where the user is trying to set the collection to None
|
||||||
if instance.collection is not None and instance.collection.user_id != user:
|
if instance.collection is not None and instance.collection.user != user:
|
||||||
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
||||||
|
|
||||||
# Perform the update
|
# Perform the update
|
||||||
|
@ -94,11 +94,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
if new_collection is not None and new_collection!=instance.collection:
|
if new_collection is not None and new_collection!=instance.collection:
|
||||||
# Check if the user is the owner of the new collection
|
# Check if the user is the owner of the new collection
|
||||||
if new_collection.user_id != user or instance.user_id != user:
|
if new_collection.user != user or instance.user != user:
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
elif new_collection is None:
|
elif new_collection is None:
|
||||||
# Handle the case where the user is trying to set the collection to None
|
# Handle the case where the user is trying to set the collection to None
|
||||||
if instance.collection is not None and instance.collection.user_id != user:
|
if instance.collection is not None and instance.collection.user != user:
|
||||||
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
||||||
|
|
||||||
# Perform the update
|
# Perform the update
|
||||||
|
@ -119,12 +119,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
|
||||||
if collection:
|
if collection:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
# Check if the user is the owner or is in the shared_with list
|
# Check if the user is the owner or is in the shared_with list
|
||||||
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
|
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
|
||||||
# Return an error response if the user does not have permission
|
# Return an error response if the user does not have permission
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
# if collection the owner of the adventure is the owner of the collection
|
# if collection the owner of the adventure is the owner of the collection
|
||||||
serializer.save(user_id=collection.user_id)
|
serializer.save(user=collection.user)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save the adventure with the current user as the owner
|
# Save the adventure with the current user as the owner
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
|
@ -4,7 +4,7 @@ from django.db import transaction
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from adventures.models import Collection, Adventure, Transportation, Note, Checklist
|
from adventures.models import Collection, Location, Transportation, Note, Checklist
|
||||||
from adventures.permissions import CollectionShared
|
from adventures.permissions import CollectionShared
|
||||||
from adventures.serializers import CollectionSerializer
|
from adventures.serializers import CollectionSerializer
|
||||||
from users.models import CustomUser as User
|
from users.models import CustomUser as User
|
||||||
|
@ -16,7 +16,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
pagination_class = pagination.StandardResultsSetPagination
|
pagination_class = pagination.StandardResultsSetPagination
|
||||||
|
|
||||||
# def get_queryset(self):
|
# def get_queryset(self):
|
||||||
# return Collection.objects.filter(Q(user_id=self.request.user.id) & Q(is_archived=False))
|
# return Collection.objects.filter(Q(user=self.request.user.id) & Q(is_archived=False))
|
||||||
|
|
||||||
def apply_sorting(self, queryset):
|
def apply_sorting(self, queryset):
|
||||||
order_by = self.request.query_params.get('order_by', 'name')
|
order_by = self.request.query_params.get('order_by', 'name')
|
||||||
|
@ -55,7 +55,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
# make sure the user is authenticated
|
# make sure the user is authenticated
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response({"error": "User is not authenticated"}, status=400)
|
return Response({"error": "User is not authenticated"}, status=400)
|
||||||
queryset = Collection.objects.filter(user_id=request.user.id, is_archived=False)
|
queryset = Collection.objects.filter(user=request.user.id, is_archived=False)
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
collections = self.paginate_and_respond(queryset, request)
|
collections = self.paginate_and_respond(queryset, request)
|
||||||
return collections
|
return collections
|
||||||
|
@ -66,7 +66,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
return Response({"error": "User is not authenticated"}, status=400)
|
return Response({"error": "User is not authenticated"}, status=400)
|
||||||
|
|
||||||
queryset = Collection.objects.filter(
|
queryset = Collection.objects.filter(
|
||||||
Q(user_id=request.user.id)
|
Q(user=request.user.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
|
@ -80,7 +80,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
return Response({"error": "User is not authenticated"}, status=400)
|
return Response({"error": "User is not authenticated"}, status=400)
|
||||||
|
|
||||||
queryset = Collection.objects.filter(
|
queryset = Collection.objects.filter(
|
||||||
Q(user_id=request.user.id) & Q(is_archived=True)
|
Q(user=request.user.id) & Q(is_archived=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
|
@ -99,7 +99,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
if 'collection' in serializer.validated_data:
|
if 'collection' in serializer.validated_data:
|
||||||
new_collection = serializer.validated_data['collection']
|
new_collection = serializer.validated_data['collection']
|
||||||
# if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error
|
# if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error
|
||||||
if new_collection != instance.collection and new_collection.user_id != request.user:
|
if new_collection != instance.collection and new_collection.user != request.user:
|
||||||
return Response({"error": "User does not own the new collection"}, status=400)
|
return Response({"error": "User does not own the new collection"}, status=400)
|
||||||
|
|
||||||
# Check if the 'is_public' field is present in the update data
|
# Check if the 'is_public' field is present in the update data
|
||||||
|
@ -107,12 +107,12 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
new_public_status = serializer.validated_data['is_public']
|
new_public_status = serializer.validated_data['is_public']
|
||||||
|
|
||||||
# if is_public has changed and the user is not the owner of the collection return an error
|
# if is_public has changed and the user is not the owner of the collection return an error
|
||||||
if new_public_status != instance.is_public and instance.user_id != request.user:
|
if new_public_status != instance.is_public and instance.user != request.user:
|
||||||
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user_id}")
|
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user}")
|
||||||
return Response({"error": "User does not own the collection"}, status=400)
|
return Response({"error": "User does not own the collection"}, status=400)
|
||||||
|
|
||||||
# Get all adventures in this collection
|
# Get all adventures in this collection
|
||||||
adventures_in_collection = Adventure.objects.filter(collections=instance)
|
adventures_in_collection = Location.objects.filter(collections=instance)
|
||||||
|
|
||||||
if new_public_status:
|
if new_public_status:
|
||||||
# If collection becomes public, make all adventures public
|
# If collection becomes public, make all adventures public
|
||||||
|
@ -129,7 +129,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
adventure_ids_to_set_private.append(adventure.id)
|
adventure_ids_to_set_private.append(adventure.id)
|
||||||
|
|
||||||
# Bulk update those adventures
|
# Bulk update those adventures
|
||||||
Adventure.objects.filter(id__in=adventure_ids_to_set_private).update(is_public=False)
|
Location.objects.filter(id__in=adventure_ids_to_set_private).update(is_public=False)
|
||||||
|
|
||||||
# Update transportations, notes, and checklists related to this collection
|
# Update transportations, notes, and checklists related to this collection
|
||||||
# These still use direct ForeignKey relationships
|
# These still use direct ForeignKey relationships
|
||||||
|
@ -207,28 +207,28 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.action == 'destroy':
|
if self.action == 'destroy':
|
||||||
return Collection.objects.filter(user_id=self.request.user.id)
|
return Collection.objects.filter(user=self.request.user.id)
|
||||||
|
|
||||||
if self.action in ['update', 'partial_update']:
|
if self.action in ['update', 'partial_update']:
|
||||||
return Collection.objects.filter(
|
return Collection.objects.filter(
|
||||||
Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)
|
Q(user=self.request.user.id) | Q(shared_with=self.request.user)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
if self.action == 'retrieve':
|
if self.action == 'retrieve':
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
return Collection.objects.filter(is_public=True)
|
return Collection.objects.filter(is_public=True)
|
||||||
return Collection.objects.filter(
|
return Collection.objects.filter(
|
||||||
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)
|
Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
# For list action, include collections owned by the user or shared with the user, that are not archived
|
# For list action, include collections owned by the user or shared with the user, that are not archived
|
||||||
return Collection.objects.filter(
|
return Collection.objects.filter(
|
||||||
(Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
|
(Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
# This is ok because you cannot share a collection when creating it
|
# This is ok because you cannot share a collection when creating it
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
def paginate_and_respond(self, queryset, request):
|
def paginate_and_respond(self, queryset, request):
|
||||||
paginator = self.pagination_class()
|
paginator = self.pagination_class()
|
||||||
|
|
|
@ -3,7 +3,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.contrib.postgres.search import SearchVector, SearchQuery
|
from django.contrib.postgres.search import SearchVector, SearchQuery
|
||||||
from adventures.models import Adventure, Collection
|
from adventures.models import Location, Collection
|
||||||
from adventures.serializers import AdventureSerializer, CollectionSerializer
|
from adventures.serializers import AdventureSerializer, CollectionSerializer
|
||||||
from worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion
|
from worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion
|
||||||
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer
|
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer
|
||||||
|
@ -31,14 +31,14 @@ class GlobalSearchView(viewsets.ViewSet):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Adventures: Full-Text Search
|
# Adventures: Full-Text Search
|
||||||
adventures = Adventure.objects.annotate(
|
adventures = Location.objects.annotate(
|
||||||
search=SearchVector('name', 'description', 'location')
|
search=SearchVector('name', 'description', 'location')
|
||||||
).filter(search=SearchQuery(search_term), user_id=request.user)
|
).filter(search=SearchQuery(search_term), user=request.user)
|
||||||
results["adventures"] = AdventureSerializer(adventures, many=True).data
|
results["adventures"] = AdventureSerializer(adventures, many=True).data
|
||||||
|
|
||||||
# Collections: Partial Match Search
|
# Collections: Partial Match Search
|
||||||
collections = Collection.objects.filter(
|
collections = Collection.objects.filter(
|
||||||
Q(name__icontains=search_term) & Q(user_id=request.user)
|
Q(name__icontains=search_term) & Q(user=request.user)
|
||||||
)
|
)
|
||||||
results["collections"] = CollectionSerializer(collections, many=True).data
|
results["collections"] = CollectionSerializer(collections, many=True).data
|
||||||
|
|
||||||
|
@ -64,10 +64,10 @@ class GlobalSearchView(viewsets.ViewSet):
|
||||||
results["cities"] = CitySerializer(cities, many=True).data
|
results["cities"] = CitySerializer(cities, many=True).data
|
||||||
|
|
||||||
# Visited Regions and Cities
|
# Visited Regions and Cities
|
||||||
visited_regions = VisitedRegion.objects.filter(user_id=request.user)
|
visited_regions = VisitedRegion.objects.filter(user=request.user)
|
||||||
results["visited_regions"] = VisitedRegionSerializer(visited_regions, many=True).data
|
results["visited_regions"] = VisitedRegionSerializer(visited_regions, many=True).data
|
||||||
|
|
||||||
visited_cities = VisitedCity.objects.filter(user_id=request.user)
|
visited_cities = VisitedCity.objects.filter(user=request.user)
|
||||||
results["visited_cities"] = VisitedCitySerializer(visited_cities, many=True).data
|
results["visited_cities"] = VisitedCitySerializer(visited_cities, many=True).data
|
||||||
|
|
||||||
return Response(results)
|
return Response(results)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from icalendar import Calendar, Event, vText, vCalAddress
|
from icalendar import Calendar, Event, vText, vCalAddress
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from adventures.models import Adventure
|
from adventures.models import Location
|
||||||
from adventures.serializers import AdventureSerializer
|
from adventures.serializers import AdventureSerializer
|
||||||
|
|
||||||
class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
|
class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
|
||||||
|
@ -12,11 +12,10 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
@action(detail=False, methods=['get'])
|
@action(detail=False, methods=['get'])
|
||||||
def generate(self, request):
|
def generate(self, request):
|
||||||
adventures = Adventure.objects.filter(user_id=request.user)
|
adventures = Location.objects.filter(user=request.user)
|
||||||
serializer = AdventureSerializer(adventures, many=True)
|
serializer = AdventureSerializer(adventures, many=True)
|
||||||
user = request.user
|
user = request.user
|
||||||
name = f"{user.first_name} {user.last_name}"
|
name = f"{user.first_name} {user.last_name}"
|
||||||
print(serializer.data)
|
|
||||||
|
|
||||||
cal = Calendar()
|
cal = Calendar()
|
||||||
cal.add('prodid', '-//My Adventure Calendar//example.com//')
|
cal.add('prodid', '-//My Adventure Calendar//example.com//')
|
||||||
|
|
|
@ -17,7 +17,7 @@ class LodgingViewSet(viewsets.ModelViewSet):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||||
queryset = Lodging.objects.filter(
|
queryset = Lodging.objects.filter(
|
||||||
Q(user_id=request.user.id)
|
Q(user=request.user.id)
|
||||||
)
|
)
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
@ -27,11 +27,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
|
||||||
if self.action == 'retrieve':
|
if self.action == 'retrieve':
|
||||||
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
|
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
|
||||||
return Lodging.objects.filter(
|
return Lodging.objects.filter(
|
||||||
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id)
|
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
# For other actions, include user's own adventures and shared adventures
|
# For other actions, include user's own adventures and shared adventures
|
||||||
return Lodging.objects.filter(
|
return Lodging.objects.filter(
|
||||||
Q(user_id=user.id) | Q(collection__shared_with=user.id)
|
Q(user=user.id) | Q(collection__shared_with=user.id)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
|
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
@ -48,11 +48,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
if new_collection is not None and new_collection != instance.collection:
|
if new_collection is not None and new_collection != instance.collection:
|
||||||
# Check if the user is the owner of the new collection
|
# Check if the user is the owner of the new collection
|
||||||
if new_collection.user_id != user or instance.user_id != user:
|
if new_collection.user != user or instance.user != user:
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
elif new_collection is None:
|
elif new_collection is None:
|
||||||
# Handle the case where the user is trying to set the collection to None
|
# Handle the case where the user is trying to set the collection to None
|
||||||
if instance.collection is not None and instance.collection.user_id != user:
|
if instance.collection is not None and instance.collection.user != user:
|
||||||
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
||||||
|
|
||||||
# Perform the update
|
# Perform the update
|
||||||
|
@ -73,12 +73,12 @@ class LodgingViewSet(viewsets.ModelViewSet):
|
||||||
if collection:
|
if collection:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
# Check if the user is the owner or is in the shared_with list
|
# Check if the user is the owner or is in the shared_with list
|
||||||
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
|
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
|
||||||
# Return an error response if the user does not have permission
|
# Return an error response if the user does not have permission
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
# if collection the owner of the adventure is the owner of the collection
|
# if collection the owner of the adventure is the owner of the collection
|
||||||
serializer.save(user_id=collection.user_id)
|
serializer.save(user=collection.user)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save the adventure with the current user as the owner
|
# Save the adventure with the current user as the owner
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
|
@ -24,7 +24,7 @@ class NoteViewSet(viewsets.ModelViewSet):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response({"error": "User is not authenticated"}, status=400)
|
return Response({"error": "User is not authenticated"}, status=400)
|
||||||
queryset = Note.objects.filter(
|
queryset = Note.objects.filter(
|
||||||
Q(user_id=request.user.id)
|
Q(user=request.user.id)
|
||||||
)
|
)
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
@ -41,12 +41,12 @@ class NoteViewSet(viewsets.ModelViewSet):
|
||||||
if self.action == 'retrieve':
|
if self.action == 'retrieve':
|
||||||
# For individual adventure retrieval, include public adventures
|
# For individual adventure retrieval, include public adventures
|
||||||
return Note.objects.filter(
|
return Note.objects.filter(
|
||||||
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
else:
|
else:
|
||||||
# For other actions, include user's own adventures and shared adventures
|
# For other actions, include user's own adventures and shared adventures
|
||||||
return Note.objects.filter(
|
return Note.objects.filter(
|
||||||
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
|
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
@ -65,11 +65,11 @@ class NoteViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
if new_collection is not None and new_collection!=instance.collection:
|
if new_collection is not None and new_collection!=instance.collection:
|
||||||
# Check if the user is the owner of the new collection
|
# Check if the user is the owner of the new collection
|
||||||
if new_collection.user_id != user or instance.user_id != user:
|
if new_collection.user != user or instance.user != user:
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
elif new_collection is None:
|
elif new_collection is None:
|
||||||
# Handle the case where the user is trying to set the collection to None
|
# Handle the case where the user is trying to set the collection to None
|
||||||
if instance.collection is not None and instance.collection.user_id != user:
|
if instance.collection is not None and instance.collection.user != user:
|
||||||
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
||||||
|
|
||||||
# Perform the update
|
# Perform the update
|
||||||
|
@ -94,11 +94,11 @@ class NoteViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
if new_collection is not None and new_collection!=instance.collection:
|
if new_collection is not None and new_collection!=instance.collection:
|
||||||
# Check if the user is the owner of the new collection
|
# Check if the user is the owner of the new collection
|
||||||
if new_collection.user_id != user or instance.user_id != user:
|
if new_collection.user != user or instance.user != user:
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
elif new_collection is None:
|
elif new_collection is None:
|
||||||
# Handle the case where the user is trying to set the collection to None
|
# Handle the case where the user is trying to set the collection to None
|
||||||
if instance.collection is not None and instance.collection.user_id != user:
|
if instance.collection is not None and instance.collection.user != user:
|
||||||
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
||||||
|
|
||||||
# Perform the update
|
# Perform the update
|
||||||
|
@ -119,12 +119,12 @@ class NoteViewSet(viewsets.ModelViewSet):
|
||||||
if collection:
|
if collection:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
# Check if the user is the owner or is in the shared_with list
|
# Check if the user is the owner or is in the shared_with list
|
||||||
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
|
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
|
||||||
# Return an error response if the user does not have permission
|
# Return an error response if the user does not have permission
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
# if collection the owner of the adventure is the owner of the collection
|
# if collection the owner of the adventure is the owner of the collection
|
||||||
serializer.save(user_id=collection.user_id)
|
serializer.save(user=collection.user)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save the adventure with the current user as the owner
|
# Save the adventure with the current user as the owner
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from worldtravel.models import Region, City, VisitedRegion, VisitedCity
|
from worldtravel.models import Region, City, VisitedRegion, VisitedCity
|
||||||
from adventures.models import Adventure
|
from adventures.models import Location
|
||||||
from adventures.serializers import AdventureSerializer
|
from adventures.serializers import AdventureSerializer
|
||||||
import requests
|
import requests
|
||||||
from adventures.geocoding import reverse_geocode
|
from adventures.geocoding import reverse_geocode
|
||||||
|
@ -52,7 +52,7 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
|
||||||
new_regions = {}
|
new_regions = {}
|
||||||
new_city_count = 0
|
new_city_count = 0
|
||||||
new_cities = {}
|
new_cities = {}
|
||||||
adventures = Adventure.objects.filter(user_id=self.request.user)
|
adventures = Location.objects.filter(user=self.request.user)
|
||||||
serializer = AdventureSerializer(adventures, many=True)
|
serializer = AdventureSerializer(adventures, many=True)
|
||||||
for adventure, serialized_adventure in zip(adventures, serializer.data):
|
for adventure, serialized_adventure in zip(adventures, serializer.data):
|
||||||
if serialized_adventure['is_visited'] == True:
|
if serialized_adventure['is_visited'] == True:
|
||||||
|
@ -69,18 +69,18 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
|
||||||
# data already contains region_id and city_id
|
# data already contains region_id and city_id
|
||||||
if 'region_id' in data and data['region_id'] is not None:
|
if 'region_id' in data and data['region_id'] is not None:
|
||||||
region = Region.objects.filter(id=data['region_id']).first()
|
region = Region.objects.filter(id=data['region_id']).first()
|
||||||
visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first()
|
visited_region = VisitedRegion.objects.filter(region=region, user=self.request.user).first()
|
||||||
if not visited_region:
|
if not visited_region:
|
||||||
visited_region = VisitedRegion(region=region, user_id=self.request.user)
|
visited_region = VisitedRegion(region=region, user=self.request.user)
|
||||||
visited_region.save()
|
visited_region.save()
|
||||||
new_region_count += 1
|
new_region_count += 1
|
||||||
new_regions[region.id] = region.name
|
new_regions[region.id] = region.name
|
||||||
|
|
||||||
if 'city_id' in data and data['city_id'] is not None:
|
if 'city_id' in data and data['city_id'] is not None:
|
||||||
city = City.objects.filter(id=data['city_id']).first()
|
city = City.objects.filter(id=data['city_id']).first()
|
||||||
visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first()
|
visited_city = VisitedCity.objects.filter(city=city, user=self.request.user).first()
|
||||||
if not visited_city:
|
if not visited_city:
|
||||||
visited_city = VisitedCity(city=city, user_id=self.request.user)
|
visited_city = VisitedCity(city=city, user=self.request.user)
|
||||||
visited_city.save()
|
visited_city.save()
|
||||||
new_city_count += 1
|
new_city_count += 1
|
||||||
new_cities[city.id] = city.name
|
new_cities[city.id] = city.name
|
||||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion
|
from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion
|
||||||
from adventures.models import Adventure, Collection
|
from adventures.models import Location, Collection
|
||||||
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
|
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
@ -26,18 +26,18 @@ class StatsViewSet(viewsets.ViewSet):
|
||||||
user.email = None
|
user.email = None
|
||||||
|
|
||||||
# get the counts for the user
|
# get the counts for the user
|
||||||
adventure_count = Adventure.objects.filter(
|
adventure_count = Location.objects.filter(
|
||||||
user_id=user.id).count()
|
user=user.id).count()
|
||||||
trips_count = Collection.objects.filter(
|
trips_count = Collection.objects.filter(
|
||||||
user_id=user.id).count()
|
user=user.id).count()
|
||||||
visited_city_count = VisitedCity.objects.filter(
|
visited_city_count = VisitedCity.objects.filter(
|
||||||
user_id=user.id).count()
|
user=user.id).count()
|
||||||
total_cities = City.objects.count()
|
total_cities = City.objects.count()
|
||||||
visited_region_count = VisitedRegion.objects.filter(
|
visited_region_count = VisitedRegion.objects.filter(
|
||||||
user_id=user.id).count()
|
user=user.id).count()
|
||||||
total_regions = Region.objects.count()
|
total_regions = Region.objects.count()
|
||||||
visited_country_count = VisitedRegion.objects.filter(
|
visited_country_count = VisitedRegion.objects.filter(
|
||||||
user_id=user.id).values('region__country').distinct().count()
|
user=user.id).values('region__country').distinct().count()
|
||||||
total_countries = Country.objects.count()
|
total_countries = Country.objects.count()
|
||||||
return Response({
|
return Response({
|
||||||
'adventure_count': adventure_count,
|
'adventure_count': adventure_count,
|
||||||
|
|
|
@ -17,7 +17,7 @@ class TransportationViewSet(viewsets.ModelViewSet):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||||
queryset = Transportation.objects.filter(
|
queryset = Transportation.objects.filter(
|
||||||
Q(user_id=request.user.id)
|
Q(user=request.user.id)
|
||||||
)
|
)
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
@ -27,11 +27,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
|
||||||
if self.action == 'retrieve':
|
if self.action == 'retrieve':
|
||||||
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
|
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
|
||||||
return Transportation.objects.filter(
|
return Transportation.objects.filter(
|
||||||
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id)
|
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
# For other actions, include user's own adventures and shared adventures
|
# For other actions, include user's own adventures and shared adventures
|
||||||
return Transportation.objects.filter(
|
return Transportation.objects.filter(
|
||||||
Q(user_id=user.id) | Q(collection__shared_with=user.id)
|
Q(user=user.id) | Q(collection__shared_with=user.id)
|
||||||
).distinct().order_by('-updated_at')
|
).distinct().order_by('-updated_at')
|
||||||
|
|
||||||
def partial_update(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
@ -48,11 +48,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
if new_collection is not None and new_collection != instance.collection:
|
if new_collection is not None and new_collection != instance.collection:
|
||||||
# Check if the user is the owner of the new collection
|
# Check if the user is the owner of the new collection
|
||||||
if new_collection.user_id != user or instance.user_id != user:
|
if new_collection.user != user or instance.user != user:
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
elif new_collection is None:
|
elif new_collection is None:
|
||||||
# Handle the case where the user is trying to set the collection to None
|
# Handle the case where the user is trying to set the collection to None
|
||||||
if instance.collection is not None and instance.collection.user_id != user:
|
if instance.collection is not None and instance.collection.user != user:
|
||||||
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
|
||||||
|
|
||||||
# Perform the update
|
# Perform the update
|
||||||
|
@ -73,12 +73,12 @@ class TransportationViewSet(viewsets.ModelViewSet):
|
||||||
if collection:
|
if collection:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
# Check if the user is the owner or is in the shared_with list
|
# Check if the user is the owner or is in the shared_with list
|
||||||
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
|
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
|
||||||
# Return an error response if the user does not have permission
|
# Return an error response if the user does not have permission
|
||||||
raise PermissionDenied("You do not have permission to use this collection.")
|
raise PermissionDenied("You do not have permission to use this collection.")
|
||||||
# if collection the owner of the adventure is the owner of the collection
|
# if collection the owner of the adventure is the owner of the collection
|
||||||
serializer.save(user_id=collection.user_id)
|
serializer.save(user=collection.user)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save the adventure with the current user as the owner
|
# Save the adventure with the current user as the owner
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
|
@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated
|
||||||
import requests
|
import requests
|
||||||
from rest_framework.pagination import PageNumberPagination
|
from rest_framework.pagination import PageNumberPagination
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from adventures.models import AdventureImage
|
from adventures.models import LocationImage
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
import logging
|
import logging
|
||||||
|
@ -253,11 +253,11 @@ class ImmichIntegrationView(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
GET an Immich image using the integration and asset ID.
|
GET an Immich image using the integration and asset ID.
|
||||||
Access levels (in order of priority):
|
Access levels (in order of priority):
|
||||||
1. Public adventures: accessible by anyone
|
1. Public locations: accessible by anyone
|
||||||
2. Private adventures in public collections: accessible by anyone
|
2. Private locations in public collections: accessible by anyone
|
||||||
3. Private adventures in private collections shared with user: accessible by shared users
|
3. Private locations in private collections shared with user: accessible by shared users
|
||||||
4. Private adventures: accessible only to the owner
|
4. Private locations: accessible only to the owner
|
||||||
5. No AdventureImage: owner can still view via integration
|
5. No LocationImage: owner can still view via integration
|
||||||
"""
|
"""
|
||||||
if not imageid or not integration_id:
|
if not imageid or not integration_id:
|
||||||
return Response({
|
return Response({
|
||||||
|
@ -268,31 +268,31 @@ class ImmichIntegrationView(viewsets.ViewSet):
|
||||||
|
|
||||||
# Lookup integration and user
|
# Lookup integration and user
|
||||||
integration = get_object_or_404(ImmichIntegration, id=integration_id)
|
integration = get_object_or_404(ImmichIntegration, id=integration_id)
|
||||||
owner_id = integration.user_id
|
owner_id = integration.user
|
||||||
|
|
||||||
# Try to find the image entry with collections and sharing information
|
# Try to find the image entry with collections and sharing information
|
||||||
image_entry = (
|
image_entry = (
|
||||||
AdventureImage.objects
|
LocationImage.objects
|
||||||
.filter(immich_id=imageid, user_id=owner_id)
|
.filter(immich_id=imageid, user=owner_id)
|
||||||
.select_related('adventure')
|
.select_related('location')
|
||||||
.prefetch_related('adventure__collections', 'adventure__collections__shared_with')
|
.prefetch_related('location__collections', 'location__collections__shared_with')
|
||||||
.order_by('-adventure__is_public') # Public adventures first
|
.order_by('-location__is_public') # Public locations first
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Access control
|
# Access control
|
||||||
if image_entry:
|
if image_entry:
|
||||||
adventure = image_entry.adventure
|
location = image_entry.location
|
||||||
collections = adventure.collections.all()
|
collections = location.collections.all()
|
||||||
|
|
||||||
# Determine access level
|
# Determine access level
|
||||||
is_authorized = False
|
is_authorized = False
|
||||||
|
|
||||||
# Level 1: Public adventure (highest priority)
|
# Level 1: Public location (highest priority)
|
||||||
if adventure.is_public:
|
if location.is_public:
|
||||||
is_authorized = True
|
is_authorized = True
|
||||||
|
|
||||||
# Level 2: Private adventure in any public collection
|
# Level 2: Private location in any public collection
|
||||||
elif any(collection.is_public for collection in collections):
|
elif any(collection.is_public for collection in collections):
|
||||||
is_authorized = True
|
is_authorized = True
|
||||||
|
|
||||||
|
@ -308,15 +308,15 @@ class ImmichIntegrationView(viewsets.ViewSet):
|
||||||
|
|
||||||
if not is_authorized:
|
if not is_authorized:
|
||||||
return Response({
|
return Response({
|
||||||
'message': 'This image belongs to a private adventure and you are not authorized.',
|
'message': 'This image belongs to a private location and you are not authorized.',
|
||||||
'error': True,
|
'error': True,
|
||||||
'code': 'immich.permission_denied'
|
'code': 'immich.permission_denied'
|
||||||
}, status=status.HTTP_403_FORBIDDEN)
|
}, status=status.HTTP_403_FORBIDDEN)
|
||||||
else:
|
else:
|
||||||
# No AdventureImage exists; allow only the integration owner
|
# No LocationImage exists; allow only the integration owner
|
||||||
if not request.user.is_authenticated or request.user.id != owner_id:
|
if not request.user.is_authenticated or request.user.id != owner_id:
|
||||||
return Response({
|
return Response({
|
||||||
'message': 'Image is not linked to any adventure and you are not the owner.',
|
'message': 'Image is not linked to any location and you are not the owner.',
|
||||||
'error': True,
|
'error': True,
|
||||||
'code': 'immich.not_found'
|
'code': 'immich.not_found'
|
||||||
}, status=status.HTTP_404_NOT_FOUND)
|
}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
|
@ -6,5 +6,5 @@ def get_user_uuid(user):
|
||||||
class CustomModelSerializer(serializers.ModelSerializer):
|
class CustomModelSerializer(serializers.ModelSerializer):
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
representation = super().to_representation(instance)
|
representation = super().to_representation(instance)
|
||||||
representation['user_id'] = get_user_uuid(instance.user_id)
|
representation['user'] = get_user_uuid(instance.user)
|
||||||
return representation
|
return representation
|
|
@ -12,7 +12,7 @@ from django.contrib.auth import get_user_model
|
||||||
from .serializers import CustomUserDetailsSerializer as PublicUserSerializer
|
from .serializers import CustomUserDetailsSerializer as PublicUserSerializer
|
||||||
from allauth.socialaccount.models import SocialApp
|
from allauth.socialaccount.models import SocialApp
|
||||||
from adventures.serializers import AdventureSerializer, CollectionSerializer
|
from adventures.serializers import AdventureSerializer, CollectionSerializer
|
||||||
from adventures.models import Adventure, Collection
|
from adventures.models import Location, Collection
|
||||||
from allauth.socialaccount.models import SocialAccount
|
from allauth.socialaccount.models import SocialAccount
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
@ -99,8 +99,8 @@ class PublicUserDetailView(APIView):
|
||||||
user.email = None
|
user.email = None
|
||||||
|
|
||||||
# Get the users adventures and collections to include in the response
|
# Get the users adventures and collections to include in the response
|
||||||
adventures = Adventure.objects.filter(user_id=user, is_public=True)
|
adventures = Location.objects.filter(user=user, is_public=True)
|
||||||
collections = Collection.objects.filter(user_id=user, is_public=True)
|
collections = Collection.objects.filter(user=user, is_public=True)
|
||||||
adventure_serializer = AdventureSerializer(adventures, many=True)
|
adventure_serializer = AdventureSerializer(adventures, many=True)
|
||||||
collection_serializer = CollectionSerializer(collections, many=True)
|
collection_serializer = CollectionSerializer(collections, many=True)
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from adventures.models import Adventure
|
from adventures.models import Location
|
||||||
import time
|
import time
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Bulk geocode all adventures by triggering save on each one'
|
help = 'Bulk geocode all adventures by triggering save on each one'
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
adventures = Adventure.objects.all()
|
adventures = Location.objects.all()
|
||||||
total = adventures.count()
|
total = adventures.count()
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures'))
|
self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures'))
|
||||||
|
|
|
@ -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()
|
User = get_user_model()
|
||||||
|
|
||||||
default_user_id = 1 # Replace with an actual user ID
|
default_user = 1 # Replace with an actual user ID
|
||||||
|
|
||||||
class Country(models.Model):
|
class Country(models.Model):
|
||||||
|
|
||||||
|
@ -50,29 +50,29 @@ class City(models.Model):
|
||||||
|
|
||||||
class VisitedRegion(models.Model):
|
class VisitedRegion(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
region = models.ForeignKey(Region, on_delete=models.CASCADE)
|
region = models.ForeignKey(Region, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.region.name} ({self.region.country.country_code}) visited by: {self.user_id.username}'
|
return f'{self.region.name} ({self.region.country.country_code}) visited by: {self.user.username}'
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if VisitedRegion.objects.filter(user_id=self.user_id, region=self.region).exists():
|
if VisitedRegion.objects.filter(user=self.user, region=self.region).exists():
|
||||||
raise ValidationError("Region already visited by user.")
|
raise ValidationError("Region already visited by user.")
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class VisitedCity(models.Model):
|
class VisitedCity(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
user_id = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.CASCADE, default=default_user_id)
|
User, on_delete=models.CASCADE, default=default_user)
|
||||||
city = models.ForeignKey(City, on_delete=models.CASCADE)
|
city = models.ForeignKey(City, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.city.name} ({self.city.region.name}) visited by: {self.user_id.username}'
|
return f'{self.city.name} ({self.city.region.name}) visited by: {self.user.username}'
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if VisitedCity.objects.filter(user_id=self.user_id, city=self.city).exists():
|
if VisitedCity.objects.filter(user=self.user, city=self.city).exists():
|
||||||
raise ValidationError("City already visited by user.")
|
raise ValidationError("City already visited by user.")
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class CountrySerializer(serializers.ModelSerializer):
|
||||||
user = getattr(request, 'user', None)
|
user = getattr(request, 'user', None)
|
||||||
|
|
||||||
if user and user.is_authenticated:
|
if user and user.is_authenticated:
|
||||||
return VisitedRegion.objects.filter(region__country=obj, user_id=user).count()
|
return VisitedRegion.objects.filter(region__country=obj, user=user).count()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -62,8 +62,8 @@ class VisitedRegionSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VisitedRegion
|
model = VisitedRegion
|
||||||
fields = ['id', 'user_id', 'region', 'longitude', 'latitude', 'name']
|
fields = ['id', 'user', 'region', 'longitude', 'latitude', 'name']
|
||||||
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name']
|
read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']
|
||||||
|
|
||||||
class VisitedCitySerializer(CustomModelSerializer):
|
class VisitedCitySerializer(CustomModelSerializer):
|
||||||
longitude = serializers.DecimalField(source='city.longitude', max_digits=9, decimal_places=6, read_only=True)
|
longitude = serializers.DecimalField(source='city.longitude', max_digits=9, decimal_places=6, read_only=True)
|
||||||
|
@ -72,5 +72,5 @@ class VisitedCitySerializer(CustomModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VisitedCity
|
model = VisitedCity
|
||||||
fields = ['id', 'user_id', 'city', 'longitude', 'latitude', 'name']
|
fields = ['id', 'user', 'city', 'longitude', 'latitude', 'name']
|
||||||
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name']
|
read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']
|
|
@ -13,7 +13,7 @@ from django.contrib.gis.geos import Point
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from django.contrib.staticfiles import finders
|
from django.contrib.staticfiles import finders
|
||||||
from adventures.models import Adventure
|
from adventures.models import Location
|
||||||
|
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
|
@ -28,7 +28,7 @@ def regions_by_country(request, country_code):
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def visits_by_country(request, country_code):
|
def visits_by_country(request, country_code):
|
||||||
country = get_object_or_404(Country, country_code=country_code)
|
country = get_object_or_404(Country, country_code=country_code)
|
||||||
visits = VisitedRegion.objects.filter(region__country=country, user_id=request.user.id)
|
visits = VisitedRegion.objects.filter(region__country=country, user=request.user.id)
|
||||||
|
|
||||||
serializer = VisitedRegionSerializer(visits, many=True)
|
serializer = VisitedRegionSerializer(visits, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
@ -45,7 +45,7 @@ def cities_by_region(request, region_id):
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def visits_by_region(request, region_id):
|
def visits_by_region(request, region_id):
|
||||||
region = get_object_or_404(Region, id=region_id)
|
region = get_object_or_404(Region, id=region_id)
|
||||||
visits = VisitedCity.objects.filter(city__region=region, user_id=request.user.id)
|
visits = VisitedCity.objects.filter(city__region=region, user=request.user.id)
|
||||||
|
|
||||||
serializer = VisitedCitySerializer(visits, many=True)
|
serializer = VisitedCitySerializer(visits, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
@ -71,7 +71,7 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
# make a post action that will get all of the users adventures and check if the point is in any of the regions if so make a visited region object for that user if it does not already exist
|
# make a post action that will get all of the users adventures and check if the point is in any of the regions if so make a visited region object for that user if it does not already exist
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'])
|
||||||
def region_check_all_adventures(self, request):
|
def region_check_all_adventures(self, request):
|
||||||
adventures = Adventure.objects.filter(user_id=request.user.id, type='visited')
|
adventures = Location.objects.filter(user=request.user.id, type='visited')
|
||||||
count = 0
|
count = 0
|
||||||
for adventure in adventures:
|
for adventure in adventures:
|
||||||
if adventure.latitude is not None and adventure.longitude is not None:
|
if adventure.latitude is not None and adventure.longitude is not None:
|
||||||
|
@ -79,8 +79,8 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
point = Point(float(adventure.longitude), float(adventure.latitude), srid=4326)
|
point = Point(float(adventure.longitude), float(adventure.latitude), srid=4326)
|
||||||
region = Region.objects.filter(geometry__contains=point).first()
|
region = Region.objects.filter(geometry__contains=point).first()
|
||||||
if region:
|
if region:
|
||||||
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists():
|
if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
|
||||||
VisitedRegion.objects.create(user_id=request.user, region=region)
|
VisitedRegion.objects.create(user=request.user, region=region)
|
||||||
count += 1
|
count += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing adventure {adventure.id}: {e}")
|
print(f"Error processing adventure {adventure.id}: {e}")
|
||||||
|
@ -97,14 +97,14 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return VisitedRegion.objects.filter(user_id=self.request.user.id)
|
return VisitedRegion.objects.filter(user=self.request.user.id)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
request.data['user_id'] = request.user
|
request.data['user'] = request.user
|
||||||
if VisitedRegion.objects.filter(user_id=request.user.id, region=request.data['region']).exists():
|
if VisitedRegion.objects.filter(user=request.user.id, region=request.data['region']).exists():
|
||||||
return Response({"error": "Region already visited by user."}, status=400)
|
return Response({"error": "Region already visited by user."}, status=400)
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
@ -115,7 +115,7 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
|
||||||
def destroy(self, request, **kwargs):
|
def destroy(self, request, **kwargs):
|
||||||
# delete by region id
|
# delete by region id
|
||||||
region = get_object_or_404(Region, id=kwargs['pk'])
|
region = get_object_or_404(Region, id=kwargs['pk'])
|
||||||
visited_region = VisitedRegion.objects.filter(user_id=request.user.id, region=region)
|
visited_region = VisitedRegion.objects.filter(user=request.user.id, region=region)
|
||||||
if visited_region.exists():
|
if visited_region.exists():
|
||||||
visited_region.delete()
|
visited_region.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -127,27 +127,27 @@ class VisitedCityViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return VisitedCity.objects.filter(user_id=self.request.user.id)
|
return VisitedCity.objects.filter(user=self.request.user.id)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(user_id=self.request.user)
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
request.data['user_id'] = request.user
|
request.data['user'] = request.user
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
self.perform_create(serializer)
|
self.perform_create(serializer)
|
||||||
# if the region is not visited, visit it
|
# if the region is not visited, visit it
|
||||||
region = serializer.validated_data['city'].region
|
region = serializer.validated_data['city'].region
|
||||||
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists():
|
if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
|
||||||
VisitedRegion.objects.create(user_id=request.user, region=region)
|
VisitedRegion.objects.create(user=request.user, region=region)
|
||||||
headers = self.get_success_headers(serializer.data)
|
headers = self.get_success_headers(serializer.data)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
def destroy(self, request, **kwargs):
|
def destroy(self, request, **kwargs):
|
||||||
# delete by city id
|
# delete by city id
|
||||||
city = get_object_or_404(City, id=kwargs['pk'])
|
city = get_object_or_404(City, id=kwargs['pk'])
|
||||||
visited_city = VisitedCity.objects.filter(user_id=request.user.id, city=city)
|
visited_city = VisitedCity.objects.filter(user=request.user.id, city=city)
|
||||||
if visited_city.exists():
|
if visited_city.exists():
|
||||||
visited_city.delete()
|
visited_city.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
|
@ -280,7 +280,7 @@
|
||||||
{$t('adventures.open_details')}
|
{$t('adventures.open_details')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
{#if adventure.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<div tabindex="0" role="button" class="btn btn-square btn-sm btn-base-300">
|
<div tabindex="0" role="button" class="btn btn-square btn-sm btn-base-300">
|
||||||
<DotsHorizontal class="w-5 h-5" />
|
<DotsHorizontal class="w-5 h-5" />
|
||||||
|
@ -297,7 +297,7 @@
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{#if user?.uuid == adventure.user_id}
|
{#if user?.uuid == adventure.user}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
on:click={() => (isCollectionModalOpen = true)}
|
on:click={() => (isCollectionModalOpen = true)}
|
||||||
|
|
|
@ -110,13 +110,13 @@
|
||||||
longitude: NaN,
|
longitude: NaN,
|
||||||
location: null,
|
location: null,
|
||||||
images: [],
|
images: [],
|
||||||
user_id: null,
|
user: null,
|
||||||
category: {
|
category: {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
user_id: ''
|
user: ''
|
||||||
},
|
},
|
||||||
attachments: []
|
attachments: []
|
||||||
};
|
};
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
longitude: adventureToEdit?.longitude || NaN,
|
longitude: adventureToEdit?.longitude || NaN,
|
||||||
location: adventureToEdit?.location || null,
|
location: adventureToEdit?.location || null,
|
||||||
images: adventureToEdit?.images || [],
|
images: adventureToEdit?.images || [],
|
||||||
user_id: adventureToEdit?.user_id || null,
|
user: adventureToEdit?.user || null,
|
||||||
visits: adventureToEdit?.visits || [],
|
visits: adventureToEdit?.visits || [],
|
||||||
is_visited: adventureToEdit?.is_visited || false,
|
is_visited: adventureToEdit?.is_visited || false,
|
||||||
category: adventureToEdit?.category || {
|
category: adventureToEdit?.category || {
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
name: '',
|
name: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
user_id: ''
|
user: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
attachments: adventureToEdit?.attachments || []
|
attachments: adventureToEdit?.attachments || []
|
||||||
|
@ -245,7 +245,7 @@
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
formData.append('adventure', adventure.id);
|
formData.append('location', adventure.id);
|
||||||
formData.append('name', attachmentName);
|
formData.append('name', attachmentName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -451,7 +451,7 @@
|
||||||
name: 'general',
|
name: 'general',
|
||||||
display_name: 'General',
|
display_name: 'General',
|
||||||
icon: '🌍',
|
icon: '🌍',
|
||||||
user_id: ''
|
user: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
display_name: '',
|
display_name: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
id: '',
|
id: '',
|
||||||
user_id: '',
|
user: '',
|
||||||
num_adventures: 0
|
num_adventures: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
<Launch class="w-5 h-5" />
|
<Launch class="w-5 h-5" />
|
||||||
{$t('notes.open')}
|
{$t('notes.open')}
|
||||||
</button>
|
</button>
|
||||||
{#if checklist.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
{#if checklist.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||||
<button
|
<button
|
||||||
id="delete_adventure"
|
id="delete_adventure"
|
||||||
data-umami-event="Delete Checklist"
|
data-umami-event="Delete Checklist"
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
let warning: string | null = '';
|
let warning: string | null = '';
|
||||||
|
|
||||||
let isReadOnly =
|
let isReadOnly =
|
||||||
!(checklist && user?.uuid == checklist?.user_id) &&
|
!(checklist && user?.uuid == checklist?.user) &&
|
||||||
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
|
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
|
||||||
!!checklist;
|
!!checklist;
|
||||||
let newStatus: boolean = false;
|
let newStatus: boolean = false;
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
name: newItem,
|
name: newItem,
|
||||||
is_checked: newStatus,
|
is_checked: newStatus,
|
||||||
id: '',
|
id: '',
|
||||||
user_id: '',
|
user: '',
|
||||||
checklist: 0,
|
checklist: 0,
|
||||||
created_at: '',
|
created_at: '',
|
||||||
updated_at: ''
|
updated_at: ''
|
||||||
|
|
|
@ -170,7 +170,7 @@
|
||||||
<Launch class="w-4 h-4" />
|
<Launch class="w-4 h-4" />
|
||||||
{$t('adventures.open_details')}
|
{$t('adventures.open_details')}
|
||||||
</button>
|
</button>
|
||||||
{#if user && user.uuid == collection.user_id}
|
{#if user && user.uuid == collection.user}
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<button type="button" class="btn btn-square btn-sm btn-base-300">
|
<button type="button" class="btn btn-square btn-sm btn-base-300">
|
||||||
<DotsHorizontal class="w-5 h-5" />
|
<DotsHorizontal class="w-5 h-5" />
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
description: collectionToEdit?.description || '',
|
description: collectionToEdit?.description || '',
|
||||||
start_date: collectionToEdit?.start_date || null,
|
start_date: collectionToEdit?.start_date || null,
|
||||||
end_date: collectionToEdit?.end_date || null,
|
end_date: collectionToEdit?.end_date || null,
|
||||||
user_id: collectionToEdit?.user_id || '',
|
user: collectionToEdit?.user || '',
|
||||||
is_public: collectionToEdit?.is_public || false,
|
is_public: collectionToEdit?.is_public || false,
|
||||||
adventures: collectionToEdit?.adventures || [],
|
adventures: collectionToEdit?.adventures || [],
|
||||||
link: collectionToEdit?.link || '',
|
link: collectionToEdit?.link || '',
|
||||||
|
|
|
@ -156,7 +156,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Reservation Info -->
|
<!-- Reservation Info -->
|
||||||
{#if lodging.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
{#if lodging.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
{#if lodging.reservation_number}
|
{#if lodging.reservation_number}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
@ -174,7 +174,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
{#if lodging.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
{#if lodging.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||||
<div class="pt-4 border-t border-base-300 flex justify-end gap-2">
|
<div class="pt-4 border-t border-base-300 flex justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
class="btn btn-neutral btn-sm flex items-center gap-1"
|
class="btn btn-neutral btn-sm flex items-center gap-1"
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
function initializeLodging(lodgingToEdit: Lodging | null): Lodging {
|
function initializeLodging(lodgingToEdit: Lodging | null): Lodging {
|
||||||
return {
|
return {
|
||||||
id: lodgingToEdit?.id || '',
|
id: lodgingToEdit?.id || '',
|
||||||
user_id: lodgingToEdit?.user_id || '',
|
user: lodgingToEdit?.user || '',
|
||||||
name: lodgingToEdit?.name || '',
|
name: lodgingToEdit?.name || '',
|
||||||
type: lodgingToEdit?.type || 'other',
|
type: lodgingToEdit?.type || 'other',
|
||||||
description: lodgingToEdit?.description || '',
|
description: lodgingToEdit?.description || '',
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
<Launch class="w-5 h-5" />
|
<Launch class="w-5 h-5" />
|
||||||
{$t('notes.open')}
|
{$t('notes.open')}
|
||||||
</button>
|
</button>
|
||||||
{#if note.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
{#if note.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||||
<button
|
<button
|
||||||
id="delete_adventure"
|
id="delete_adventure"
|
||||||
data-umami-event="Delete Adventure"
|
data-umami-event="Delete Adventure"
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
let constrainDates: boolean = false;
|
let constrainDates: boolean = false;
|
||||||
|
|
||||||
let isReadOnly =
|
let isReadOnly =
|
||||||
!(note && user?.uuid == note?.user_id) &&
|
!(note && user?.uuid == note?.user) &&
|
||||||
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
|
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
|
||||||
!!note;
|
!!note;
|
||||||
|
|
||||||
|
|
|
@ -192,7 +192,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
{#if transportation.user_id === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
{#if transportation.user === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
|
||||||
<div class="pt-4 border-t border-base-300 flex justify-end gap-2">
|
<div class="pt-4 border-t border-base-300 flex justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
class="btn btn-neutral btn-sm flex items-center gap-1"
|
class="btn btn-neutral btn-sm flex items-center gap-1"
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
flight_number: transportationToEdit?.flight_number || '',
|
flight_number: transportationToEdit?.flight_number || '',
|
||||||
from_location: transportationToEdit?.from_location || '',
|
from_location: transportationToEdit?.from_location || '',
|
||||||
to_location: transportationToEdit?.to_location || '',
|
to_location: transportationToEdit?.to_location || '',
|
||||||
user_id: transportationToEdit?.user_id || '',
|
user: transportationToEdit?.user || '',
|
||||||
is_public: transportationToEdit?.is_public || false,
|
is_public: transportationToEdit?.is_public || false,
|
||||||
collection: transportationToEdit?.collection || collection.id,
|
collection: transportationToEdit?.collection || collection.id,
|
||||||
created_at: transportationToEdit?.created_at || '',
|
created_at: transportationToEdit?.created_at || '',
|
||||||
|
|
|
@ -16,7 +16,7 @@ export type User = {
|
||||||
|
|
||||||
export type Adventure = {
|
export type Adventure = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string | null;
|
user: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
location?: string | null;
|
location?: string | null;
|
||||||
activity_types?: string[] | null;
|
activity_types?: string[] | null;
|
||||||
|
@ -96,7 +96,7 @@ export type City = {
|
||||||
export type VisitedRegion = {
|
export type VisitedRegion = {
|
||||||
id: number;
|
id: number;
|
||||||
region: string;
|
region: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -105,7 +105,7 @@ export type VisitedRegion = {
|
||||||
export type VisitedCity = {
|
export type VisitedCity = {
|
||||||
id: number;
|
id: number;
|
||||||
city: string;
|
city: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -123,7 +123,7 @@ export type Point = {
|
||||||
|
|
||||||
export type Collection = {
|
export type Collection = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
is_public: boolean;
|
is_public: boolean;
|
||||||
|
@ -153,7 +153,7 @@ export type GeocodeSearchResult = {
|
||||||
|
|
||||||
export type Transportation = {
|
export type Transportation = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
type: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
|
@ -179,7 +179,7 @@ export type Transportation = {
|
||||||
|
|
||||||
export type Note = {
|
export type Note = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
name: string;
|
name: string;
|
||||||
content: string | null;
|
content: string | null;
|
||||||
links: string[] | null;
|
links: string[] | null;
|
||||||
|
@ -192,7 +192,7 @@ export type Note = {
|
||||||
|
|
||||||
export type Checklist = {
|
export type Checklist = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
name: string;
|
name: string;
|
||||||
items: ChecklistItem[];
|
items: ChecklistItem[];
|
||||||
date: string | null; // ISO 8601 date string
|
date: string | null; // ISO 8601 date string
|
||||||
|
@ -204,7 +204,7 @@ export type Checklist = {
|
||||||
|
|
||||||
export type ChecklistItem = {
|
export type ChecklistItem = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
name: string;
|
name: string;
|
||||||
is_checked: boolean;
|
is_checked: boolean;
|
||||||
checklist: number;
|
checklist: number;
|
||||||
|
@ -235,7 +235,7 @@ export type Category = {
|
||||||
name: string;
|
name: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
num_adventures?: number | null;
|
num_adventures?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -279,13 +279,13 @@ export type Attachment = {
|
||||||
file: string;
|
file: string;
|
||||||
adventure: string;
|
adventure: string;
|
||||||
extension: string;
|
extension: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Lodging = {
|
export type Lodging = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
|
|
|
@ -150,7 +150,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if adventure}
|
{#if adventure}
|
||||||
{#if data.user && data.user.uuid == adventure.user_id}
|
{#if data.user && data.user.uuid == adventure.user}
|
||||||
<div class="fixed bottom-6 right-6 z-50">
|
<div class="fixed bottom-6 right-6 z-50">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-circle w-16 h-16 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-110"
|
class="btn btn-primary btn-circle w-16 h-16 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-110"
|
||||||
|
|
|
@ -498,7 +498,7 @@
|
||||||
function recomendationToAdventure(recomendation: any) {
|
function recomendationToAdventure(recomendation: any) {
|
||||||
adventureToEdit = {
|
adventureToEdit = {
|
||||||
id: '',
|
id: '',
|
||||||
user_id: null,
|
user: null,
|
||||||
name: recomendation.name,
|
name: recomendation.name,
|
||||||
latitude: recomendation.latitude,
|
latitude: recomendation.latitude,
|
||||||
longitude: recomendation.longitude,
|
longitude: recomendation.longitude,
|
||||||
|
@ -513,7 +513,7 @@
|
||||||
icon: osmTagToEmoji(recomendation.tag),
|
icon: osmTagToEmoji(recomendation.tag),
|
||||||
id: '',
|
id: '',
|
||||||
name: recomendation.tag,
|
name: recomendation.tag,
|
||||||
user_id: ''
|
user: ''
|
||||||
},
|
},
|
||||||
attachments: []
|
attachments: []
|
||||||
};
|
};
|
||||||
|
@ -748,7 +748,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if collection && collection.id}
|
{#if collection && collection.id}
|
||||||
{#if data.user && data.user.uuid && (data.user.uuid == collection.user_id || (collection.shared_with && collection.shared_with.includes(data.user.uuid))) && !collection.is_archived}
|
{#if data.user && data.user.uuid && (data.user.uuid == collection.user || (collection.shared_with && collection.shared_with.includes(data.user.uuid))) && !collection.is_archived}
|
||||||
<div class="fixed bottom-4 right-4 z-[999]">
|
<div class="fixed bottom-4 right-4 z-[999]">
|
||||||
<div class="flex flex-row items-center justify-center gap-4">
|
<div class="flex flex-row items-center justify-center gap-4">
|
||||||
<div class="dropdown dropdown-top dropdown-end z-[999]">
|
<div class="dropdown dropdown-top dropdown-end z-[999]">
|
||||||
|
@ -760,7 +760,7 @@
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content z-[1] menu p-4 shadow bg-base-300 text-base-content rounded-box w-52 gap-4"
|
class="dropdown-content z-[1] menu p-4 shadow bg-base-300 text-base-content rounded-box w-52 gap-4"
|
||||||
>
|
>
|
||||||
{#if collection.user_id === data.user.uuid}
|
{#if collection.user === data.user.uuid}
|
||||||
<p class="text-center font-bold text-lg">{$t('adventures.link_new')}</p>
|
<p class="text-center font-bold text-lg">{$t('adventures.link_new')}</p>
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue