Add migrations for ParkPhoto and RidePhoto models with associated events

- Created ParkPhoto and ParkPhotoEvent models in the parks app, including fields for image, caption, alt text, and relationships to the Park model.
- Implemented triggers for insert and update operations on ParkPhoto to log changes in ParkPhotoEvent.
- Created RidePhoto and RidePhotoEvent models in the rides app, with similar structure and functionality as ParkPhoto.
- Added fields for photo type in RidePhoto and implemented corresponding triggers for logging changes.
- Established necessary indexes and unique constraints for both models to ensure data integrity and optimize queries.
This commit is contained in:
pacnpal
2025-08-26 14:40:46 -04:00
parent 831be6a2ee
commit e4e36c7899
133 changed files with 1321 additions and 1001 deletions

View File

@@ -106,9 +106,7 @@ class Command(BaseCommand):
)
self.created_companies[company.slug] = company
self.stdout.write(
f' {
"Created" if created else "Found"} park company: {
company.name}'
f" {'Created' if created else 'Found'} park company: {company.name}"
)
# Ride manufacturers and designers (using rides.models.Company)
@@ -201,9 +199,7 @@ class Command(BaseCommand):
)
self.created_companies[company.slug] = company
self.stdout.write(
f' {
"Created" if created else "Found"} ride company: {
company.name}'
f" {'Created' if created else 'Found'} ride company: {company.name}"
)
def create_parks(self):

View File

@@ -53,7 +53,7 @@ class Command(BaseCommand):
)
companies[operator.name] = operator
self.stdout.write(
f'{"Created" if created else "Found"} company: {operator.name}'
f"{'Created' if created else 'Found'} company: {operator.name}"
)
# Create parks with their locations
@@ -301,7 +301,7 @@ class Command(BaseCommand):
"owner": company,
},
)
self.stdout.write(f'{"Created" if created else "Found"} park: {park.name}')
self.stdout.write(f"{'Created' if created else 'Found'} park: {park.name}")
# Create location for park
if created:
@@ -328,7 +328,7 @@ class Command(BaseCommand):
defaults={"description": area_data["description"]},
)
self.stdout.write(
f'{"Created" if created else "Found"} area: {area.name} in {park.name}'
f"{'Created' if created else 'Found'} area: {area.name} in {park.name}"
)
self.stdout.write(self.style.SUCCESS("Successfully seeded initial park data"))

View File

@@ -121,8 +121,7 @@ class Command(BaseCommand):
except Exception as e:
self.logger.error(
f"Error during data cleanup: {
str(e)}",
f"Error during data cleanup: {str(e)}",
exc_info=True,
)
self.stdout.write(
@@ -205,7 +204,7 @@ class Command(BaseCommand):
if missing_tables:
self.stdout.write(
self.style.WARNING(
f'Missing tables for models: {", ".join(missing_tables)}'
f"Missing tables for models: {', '.join(missing_tables)}"
)
)
return False
@@ -353,13 +352,13 @@ class Command(BaseCommand):
)
self.park_companies[data["name"]] = company
self.stdout.write(
f' {
"Created" if created else "Found"} park company: {
company.name}'
f" {'Created' if created else 'Found'} park company: {
company.name
}"
)
except Exception as e:
self.logger.error(
f'Error creating park company {data["name"]}: {str(e)}'
f"Error creating park company {data['name']}: {str(e)}"
)
raise
@@ -378,13 +377,13 @@ class Command(BaseCommand):
)
self.ride_companies[data["name"]] = company
self.stdout.write(
f' {
"Created" if created else "Found"} ride company: {
company.name}'
f" {'Created' if created else 'Found'} ride company: {
company.name
}"
)
except Exception as e:
self.logger.error(
f'Error creating ride company {data["name"]}: {str(e)}'
f"Error creating ride company {data['name']}: {str(e)}"
)
raise
@@ -532,9 +531,7 @@ class Command(BaseCommand):
)
self.parks[park_data["name"]] = park
self.stdout.write(
f' {
"Created" if created else "Found"} park: {
park.name}'
f" {'Created' if created else 'Found'} park: {park.name}"
)
# Create location for park
@@ -556,15 +553,15 @@ class Command(BaseCommand):
park_location.save()
except Exception as e:
self.logger.error(
f'Error creating location for park {
park_data["name"]}: {
str(e)}'
f"Error creating location for park {
park_data['name']
}: {str(e)}"
)
raise
except Exception as e:
self.logger.error(
f'Error creating park {park_data["name"]}: {str(e)}'
f"Error creating park {park_data['name']}: {str(e)}"
)
raise
@@ -631,15 +628,13 @@ class Command(BaseCommand):
)
self.ride_models[model_data["name"]] = model
self.stdout.write(
f' {
"Created" if created else "Found"} ride model: {
model.name}'
f" {'Created' if created else 'Found'} ride model: {
model.name
}"
)
except Exception as e:
self.logger.error(
f'Error creating ride model {
model_data["name"]}: {
str(e)}'
f"Error creating ride model {model_data['name']}: {str(e)}"
)
raise
@@ -860,9 +855,7 @@ class Command(BaseCommand):
)
self.rides[ride_data["name"]] = ride
self.stdout.write(
f' {
"Created" if created else "Found"} ride: {
ride.name}'
f" {'Created' if created else 'Found'} ride: {ride.name}"
)
# Create roller coaster stats if provided
@@ -872,15 +865,15 @@ class Command(BaseCommand):
RollerCoasterStats.objects.create(ride=ride, **stats_data)
except Exception as e:
self.logger.error(
f'Error creating stats for ride {
ride_data["name"]}: {
str(e)}'
f"Error creating stats for ride {ride_data['name']}: {
str(e)
}"
)
raise
except Exception as e:
self.logger.error(
f'Error creating ride {ride_data["name"]}: {str(e)}'
f"Error creating ride {ride_data['name']}: {str(e)}"
)
raise
@@ -1013,16 +1006,13 @@ class Command(BaseCommand):
},
)
self.stdout.write(
f' {
"Created" if created else "Found"} area: {
area.name} in {
park.name}'
f" {'Created' if created else 'Found'} area: {
area.name
} in {park.name}"
)
except Exception as e:
self.logger.error(
f'Error creating areas for park {
area_group["park"]}: {
str(e)}'
f"Error creating areas for park {area_group['park']}: {str(e)}"
)
raise
@@ -1095,15 +1085,15 @@ class Command(BaseCommand):
},
)
self.stdout.write(
f' {
"Created" if created else "Found"} park review: {
review.title}'
f" {'Created' if created else 'Found'} park review: {
review.title
}"
)
except Exception as e:
self.logger.error(
f'Error creating park review for {
review_data["park"]}: {
str(e)}'
f"Error creating park review for {review_data['park']}: {
str(e)
}"
)
raise
@@ -1154,15 +1144,15 @@ class Command(BaseCommand):
},
)
self.stdout.write(
f' {
"Created" if created else "Found"} ride review: {
review.title}'
f" {'Created' if created else 'Found'} ride review: {
review.title
}"
)
except Exception as e:
self.logger.error(
f'Error creating ride review for {
review_data["ride"]}: {
str(e)}'
f"Error creating ride review for {review_data['ride']}: {
str(e)
}"
)
raise

View File

@@ -55,10 +55,7 @@ class Command(BaseCommand):
# Test Park model integration
self.stdout.write("\n🔍 Testing Park model integration:")
self.stdout.write(
f" Park formatted location: {
park.formatted_location}"
)
self.stdout.write(f" Park formatted location: {park.formatted_location}")
self.stdout.write(f" Park coordinates: {park.coordinates}")
# Create another location for distance testing
@@ -112,10 +109,7 @@ class Command(BaseCommand):
nearby_locations = ParkLocation.objects.filter(
point__distance_lte=(search_point, D(km=100))
)
self.stdout.write(
f" Found {
nearby_locations.count()} parks within 100km"
)
self.stdout.write(f" Found {nearby_locations.count()} parks within 100km")
for loc in nearby_locations:
self.stdout.write(f" - {loc.park.name} in {loc.city}, {loc.state}")
except Exception as e:

View File

@@ -1,6 +1,6 @@
# Generated manually for enhanced filtering performance
from django.db import migrations, models
from django.db import migrations
class Migration(migrations.Migration):

View File

@@ -11,7 +11,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [

View File

@@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("parks", "0001_initial"),
]

View File

@@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("parks", "0002_alter_parkarea_unique_together"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),

View File

@@ -8,7 +8,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("parks", "0003_add_business_constraints"),
("pghistory", "0007_auto_20250421_0444"),

View File

@@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("parks", "0001_add_filter_indexes"),
("parks", "0004_fix_pghistory_triggers"),

View File

@@ -6,7 +6,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("parks", "0005_merge_20250820_2020"),
]

View File

@@ -8,7 +8,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("parks", "0006_remove_company_insert_insert_and_more"),
("pghistory", "0007_auto_20250421_0444"),

View File

@@ -0,0 +1,188 @@
# Generated by Django 5.2.5 on 2025-08-26 17:39
import apps.parks.models.media
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("parks", "0007_companyheadquartersevent_parklocationevent_and_more"),
("pghistory", "0007_auto_20250421_0444"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="ParkPhoto",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"image",
models.ImageField(
max_length=255,
upload_to=apps.parks.models.media.park_photo_upload_path,
),
),
("caption", models.CharField(blank=True, max_length=255)),
("alt_text", models.CharField(blank=True, max_length=255)),
("is_primary", models.BooleanField(default=False)),
("is_approved", models.BooleanField(default=False)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("date_taken", models.DateTimeField(blank=True, null=True)),
(
"park",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="photos",
to="parks.park",
),
),
(
"uploaded_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="uploaded_park_photos",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ["-is_primary", "-created_at"],
},
),
migrations.CreateModel(
name="ParkPhotoEvent",
fields=[
("pgh_id", models.AutoField(primary_key=True, serialize=False)),
("pgh_created_at", models.DateTimeField(auto_now_add=True)),
("pgh_label", models.TextField(help_text="The event label.")),
("id", models.BigIntegerField()),
(
"image",
models.ImageField(
max_length=255,
upload_to=apps.parks.models.media.park_photo_upload_path,
),
),
("caption", models.CharField(blank=True, max_length=255)),
("alt_text", models.CharField(blank=True, max_length=255)),
("is_primary", models.BooleanField(default=False)),
("is_approved", models.BooleanField(default=False)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("date_taken", models.DateTimeField(blank=True, null=True)),
(
"park",
models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="parks.park",
),
),
(
"pgh_context",
models.ForeignKey(
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="pghistory.context",
),
),
(
"pgh_obj",
models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="events",
to="parks.parkphoto",
),
),
(
"uploaded_by",
models.ForeignKey(
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"abstract": False,
},
),
migrations.AddIndex(
model_name="parkphoto",
index=models.Index(
fields=["park", "is_primary"], name="parks_parkp_park_id_eda26e_idx"
),
),
migrations.AddIndex(
model_name="parkphoto",
index=models.Index(
fields=["park", "is_approved"], name="parks_parkp_park_id_5fe576_idx"
),
),
migrations.AddIndex(
model_name="parkphoto",
index=models.Index(
fields=["created_at"], name="parks_parkp_created_033dc3_idx"
),
),
migrations.AddConstraint(
model_name="parkphoto",
constraint=models.UniqueConstraint(
condition=models.Q(("is_primary", True)),
fields=("park",),
name="unique_primary_park_photo",
),
),
pgtrigger.migrations.AddTrigger(
model_name="parkphoto",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "parks_parkphotoevent" ("alt_text", "caption", "created_at", "date_taken", "id", "image", "is_approved", "is_primary", "park_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "updated_at", "uploaded_by_id") VALUES (NEW."alt_text", NEW."caption", NEW."created_at", NEW."date_taken", NEW."id", NEW."image", NEW."is_approved", NEW."is_primary", NEW."park_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."updated_at", NEW."uploaded_by_id"); RETURN NULL;',
hash="eeeb8afb335eb66cb4550a0f5abfaf7280472827",
operation="INSERT",
pgid="pgtrigger_insert_insert_e2033",
table="parks_parkphoto",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="parkphoto",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "parks_parkphotoevent" ("alt_text", "caption", "created_at", "date_taken", "id", "image", "is_approved", "is_primary", "park_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "updated_at", "uploaded_by_id") VALUES (NEW."alt_text", NEW."caption", NEW."created_at", NEW."date_taken", NEW."id", NEW."image", NEW."is_approved", NEW."is_primary", NEW."park_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."updated_at", NEW."uploaded_by_id"); RETURN NULL;',
hash="bd95069068ba9e1a78708a0a9cc73d6507fab691",
operation="UPDATE",
pgid="pgtrigger_update_update_42711",
table="parks_parkphoto",
when="AFTER",
),
),
),
]

View File

@@ -14,6 +14,7 @@ from .location import ParkLocation
from .reviews import ParkReview
from .companies import Company, CompanyHeadquarters
from .media import ParkPhoto
# Alias Company as Operator for clarity
Operator = Company

View File

@@ -8,7 +8,6 @@ from .parks import Park
@pghistory.track()
class ParkArea(TrackedModel):
# Import managers
from ..managers import ParkAreaManager

View File

@@ -7,7 +7,6 @@ import pghistory
@pghistory.track()
class Company(TrackedModel):
# Import managers
from ..managers import CompanyManager
@@ -107,13 +106,7 @@ class CompanyHeadquarters(models.Model):
components.append(self.postal_code)
if self.country and self.country != "USA":
components.append(self.country)
return (
", ".join(components)
if components
else f"{
self.city}, {
self.country}"
)
return ", ".join(components) if components else f"{self.city}, {self.country}"
@property
def location_display(self):

View File

@@ -7,7 +7,6 @@ This module contains media models specific to parks domain.
from typing import Any, Optional, cast
from django.db import models
from django.conf import settings
from django.utils import timezone
from apps.core.history import TrackedModel
from apps.core.services.media_service import MediaService
import pghistory
@@ -15,16 +14,14 @@ import pghistory
def park_photo_upload_path(instance: models.Model, filename: str) -> str:
"""Generate upload path for park photos."""
photo = cast('ParkPhoto', instance)
photo = cast("ParkPhoto", instance)
park = photo.park
if park is None:
raise ValueError("Park cannot be None")
return MediaService.generate_upload_path(
domain="park",
identifier=park.slug,
filename=filename
domain="park", identifier=park.slug, filename=filename
)
@@ -33,9 +30,7 @@ class ParkPhoto(TrackedModel):
"""Photo model specific to parks."""
park = models.ForeignKey(
'parks.Park',
on_delete=models.CASCADE,
related_name='photos'
"parks.Park", on_delete=models.CASCADE, related_name="photos"
)
image = models.ImageField(
@@ -72,9 +67,9 @@ class ParkPhoto(TrackedModel):
constraints = [
# Only one primary photo per park
models.UniqueConstraint(
fields=['park'],
fields=["park"],
condition=models.Q(is_primary=True),
name='unique_primary_park_photo'
name="unique_primary_park_photo",
)
]

View File

@@ -13,7 +13,6 @@ if TYPE_CHECKING:
@pghistory.track()
class Park(TrackedModel):
# Import managers
from ..managers import ParkManager
@@ -226,7 +225,8 @@ class Park(TrackedModel):
if historical:
print(
f"Found historical slug record for object_id: {
historical.object_id}"
historical.object_id
}"
)
try:
park = cls.objects.get(pk=historical.object_id)
@@ -250,7 +250,8 @@ class Park(TrackedModel):
if historical_event:
print(
f"Found pghistory event for pgh_obj_id: {
historical_event.pgh_obj_id}"
historical_event.pgh_obj_id
}"
)
try:
park = cls.objects.get(pk=historical_event.pgh_obj_id)

View File

@@ -7,7 +7,6 @@ import pghistory
@pghistory.track()
class ParkReview(TrackedModel):
# Import managers
from ..managers import ParkReviewManager

View File

@@ -3,5 +3,11 @@ from .park_management import ParkService
from .location_service import ParkLocationService
from .filter_service import ParkFilterService
from .media_service import ParkMediaService
__all__ = ["RoadTripService", "ParkService",
"ParkLocationService", "ParkFilterService", "ParkMediaService"]
__all__ = [
"RoadTripService",
"ParkService",
"ParkLocationService",
"ParkFilterService",
"ParkMediaService",
]

View File

@@ -4,8 +4,7 @@ Handles geocoding, reverse geocoding, and location search for parks.
"""
import requests
from typing import List, Dict, Any, Optional, Tuple
from django.conf import settings
from typing import List, Dict, Any, Optional
from django.core.cache import cache
from django.db import transaction
import logging

View File

@@ -27,7 +27,7 @@ class ParkMediaService:
caption: str = "",
alt_text: str = "",
is_primary: bool = False,
auto_approve: bool = False
auto_approve: bool = False,
) -> ParkPhoto:
"""
Upload a photo for a park.
@@ -64,7 +64,7 @@ class ParkMediaService:
alt_text=alt_text,
is_primary=is_primary,
is_approved=auto_approve,
uploaded_by=user
uploaded_by=user,
)
# Extract EXIF date
@@ -77,9 +77,7 @@ class ParkMediaService:
@staticmethod
def get_park_photos(
park: Park,
approved_only: bool = True,
primary_first: bool = True
park: Park, approved_only: bool = True, primary_first: bool = True
) -> List[ParkPhoto]:
"""
Get photos for a park.
@@ -98,9 +96,9 @@ class ParkMediaService:
queryset = queryset.filter(is_approved=True)
if primary_first:
queryset = queryset.order_by('-is_primary', '-created_at')
queryset = queryset.order_by("-is_primary", "-created_at")
else:
queryset = queryset.order_by('-created_at')
queryset = queryset.order_by("-created_at")
return list(queryset)
@@ -190,7 +188,8 @@ class ParkMediaService:
photo.delete()
logger.info(
f"Photo {photo_id} deleted from park {park_slug} by user {deleted_by.username}")
f"Photo {photo_id} deleted from park {park_slug} by user {deleted_by.username}"
)
return True
except Exception as e:
logger.error(f"Failed to delete photo {photo.pk}: {str(e)}")
@@ -214,7 +213,7 @@ class ParkMediaService:
"approved_photos": photos.filter(is_approved=True).count(),
"pending_photos": photos.filter(is_approved=False).count(),
"has_primary": photos.filter(is_primary=True).exists(),
"recent_uploads": photos.order_by('-created_at')[:5].count()
"recent_uploads": photos.order_by("-created_at")[:5].count(),
}
@staticmethod
@@ -237,5 +236,6 @@ class ParkMediaService:
approved_count += 1
logger.info(
f"Bulk approved {approved_count} photos by user {approved_by.username}")
f"Bulk approved {approved_count} photos by user {approved_by.username}"
)
return approved_count

View File

@@ -192,8 +192,7 @@ class RoadTripService:
time.sleep(wait_time)
else:
raise OSMAPIException(
f"Failed to make request after {
self.max_retries} attempts: {e}"
f"Failed to make request after {self.max_retries} attempts: {e}"
)
def geocode_address(self, address: str) -> Optional[Coordinates]:
@@ -244,9 +243,7 @@ class RoadTripService:
)
logger.info(
f"Geocoded '{address}' to {
coords.latitude}, {
coords.longitude}"
f"Geocoded '{address}' to {coords.latitude}, {coords.longitude}"
)
return coords
else:
@@ -274,22 +271,18 @@ class RoadTripService:
return None
# Check cache first
cache_key = f"roadtrip:route:{
start_coords.latitude},{
start_coords.longitude}:{
end_coords.latitude},{
end_coords.longitude}"
cache_key = f"roadtrip:route:{start_coords.latitude},{start_coords.longitude}:{
end_coords.latitude
},{end_coords.longitude}"
cached_result = cache.get(cache_key)
if cached_result:
return RouteInfo(**cached_result)
try:
# Format coordinates for OSRM (lon,lat format)
coords_string = f"{
start_coords.longitude},{
start_coords.latitude};{
end_coords.longitude},{
end_coords.latitude}"
coords_string = f"{start_coords.longitude},{start_coords.latitude};{
end_coords.longitude
},{end_coords.latitude}"
url = f"{self.osrm_base_url}/{coords_string}"
params = {
@@ -326,9 +319,9 @@ class RoadTripService:
)
logger.info(
f"Route calculated: {
route_info.formatted_distance}, {
route_info.formatted_duration}"
f"Route calculated: {route_info.formatted_distance}, {
route_info.formatted_duration
}"
)
return route_info
else:
@@ -350,11 +343,13 @@ class RoadTripService:
Calculate straight-line distance as fallback when routing fails.
"""
# Haversine formula for great-circle distance
lat1, lon1 = math.radians(start_coords.latitude), math.radians(
start_coords.longitude
lat1, lon1 = (
math.radians(start_coords.latitude),
math.radians(start_coords.longitude),
)
lat2, lon2 = math.radians(end_coords.latitude), math.radians(
end_coords.longitude
lat2, lon2 = (
math.radians(end_coords.latitude),
math.radians(end_coords.longitude),
)
dlat = lat2 - lat1
@@ -696,10 +691,7 @@ class RoadTripService:
location.set_coordinates(coords.latitude, coords.longitude)
location.save()
logger.info(
f"Geocoded park '{
park.name}' to {
coords.latitude}, {
coords.longitude}"
f"Geocoded park '{park.name}' to {coords.latitude}, {coords.longitude}"
)
return True

View File

@@ -165,7 +165,8 @@ class ParkAreaModelTests(TestCase):
with transaction.atomic():
with self.assertRaises(IntegrityError):
ParkArea.objects.create(
park=self.park, name="Test Area" # Will generate same slug
park=self.park,
name="Test Area", # Will generate same slug
)
# Should be able to use same name in different park

View File

@@ -551,14 +551,12 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
image=photo_file,
uploaded_by=self.request.user,
park=self.object,
) )
)
uploaded_count += 1
except Exception as e:
messages.error(
self.request,
f"Error uploading photo {
photo_file.name}: {
str(e)}",
f"Error uploading photo {photo_file.name}: {str(e)}",
)
messages.success(
@@ -571,7 +569,8 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
messages.error(
self.request,
f"Error creating park: {
str(e)}. Please check your input and try again.",
str(e)
}. Please check your input and try again.",
)
return self.form_invalid(form)
@@ -727,9 +726,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
except Exception as e:
messages.error(
self.request,
f"Error uploading photo {
photo_file.name}: {
str(e)}",
f"Error uploading photo {photo_file.name}: {str(e)}",
)
messages.success(
@@ -742,7 +739,8 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
messages.error(
self.request,
f"Error updating park: {
str(e)}. Please check your input and try again.",
str(e)
}. Please check your input and try again.",
)
return self.form_invalid(form)

View File

@@ -13,7 +13,7 @@ from django.urls import reverse
from .models import Park
from .services.roadtrip import RoadTripService
from apps.core.services.map_service import unified_map_service
from apps.core.services.data_structures import LocationType, MapFilters
from apps.core.services.data_structures import LocationType
JSON_DECODE_ERROR_MSG = "Invalid JSON data"
PARKS_ALONG_ROUTE_HTML = "parks/partials/parks_along_route.html"