feat: Implement ride management views and utility functions

- Added functions for checking user privileges, handling photo uploads, preparing form data, and managing form errors.
- Created views for listing, creating, updating, and displaying rides, including category-specific views.
- Integrated submission handling for ride changes and improved user feedback through messages.
- Enhanced data handling with appropriate context and queryset management for better performance and usability.
This commit is contained in:
pacnpal
2024-11-04 05:25:53 +00:00
parent ae913e757a
commit 01e0a609d2
29 changed files with 1087 additions and 925 deletions

View File

@@ -1,3 +1,4 @@
from typing import Any, Optional, Union, cast
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
@@ -6,32 +7,37 @@ from django.conf import settings
import os
from .storage import MediaStorage
from rides.models import Ride
from django.utils import timezone
def photo_upload_path(instance, filename):
def photo_upload_path(instance: models.Model, filename: str) -> str:
"""Generate upload path for photos using normalized filenames"""
# Get the content type and object
content_type = instance.content_type.model
obj = instance.content_object
photo = cast(Photo, instance)
content_type = photo.content_type.model
obj = photo.content_object
if obj is None:
raise ValueError("Content object cannot be None")
# Get object identifier (slug or id)
identifier = getattr(obj, 'slug', obj.id)
identifier = getattr(obj, 'slug', None)
if identifier is None:
identifier = obj.pk # Use pk instead of id as it's guaranteed to exist
# Get the next available number for this object
existing_photos = Photo.objects.filter(
content_type=instance.content_type,
object_id=instance.object_id
content_type=photo.content_type,
object_id=photo.object_id
).count()
next_number = existing_photos + 1
# Create normalized filename
ext = os.path.splitext(filename)[1].lower()
if not ext:
ext = '.jpg' # Default to .jpg if no extension
ext = os.path.splitext(filename)[1].lower() or '.jpg' # Default to .jpg if no extension
new_filename = f"{identifier}_{next_number}{ext}"
# If it's a ride photo, store it under the park's directory
if content_type == 'ride':
ride = Ride.objects.get(id=obj.id)
ride = cast(Ride, obj)
return f"park/{ride.park.slug}/{identifier}/{new_filename}"
# For park photos, store directly in park directory
@@ -40,7 +46,7 @@ def photo_upload_path(instance, filename):
class Photo(models.Model):
"""Generic photo model that can be attached to any model"""
image = models.ImageField(
upload_to=photo_upload_path,
upload_to=photo_upload_path, # type: ignore[arg-type]
max_length=255,
storage=MediaStorage()
)
@@ -67,13 +73,14 @@ class Photo(models.Model):
models.Index(fields=['content_type', 'object_id']),
]
def __str__(self):
def __str__(self) -> str:
return f"{self.content_type} - {self.content_object} - {self.caption or 'No caption'}"
def save(self, *args, **kwargs):
def save(self, *args: Any, **kwargs: Any) -> None:
# Set default caption if not provided
if not self.caption and self.uploaded_by:
self.caption = f"Uploaded by {self.uploaded_by.username} on {self.created_at.strftime('%B %d, %Y at %I:%M %p')}"
current_time = timezone.now()
self.caption = f"Uploaded by {self.uploaded_by.username} on {current_time.strftime('%B %d, %Y at %I:%M %p')}"
# If this is marked as primary, unmark other primary photos
if self.is_primary:
@@ -81,6 +88,6 @@ class Photo(models.Model):
content_type=self.content_type,
object_id=self.object_id,
is_primary=True
).exclude(id=self.id).update(is_primary=False)
).exclude(pk=self.pk).update(is_primary=False) # Use pk instead of id
super().save(*args, **kwargs)