mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 09:31:09 -05:00
- 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.
94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
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
|
|
from django.utils.text import slugify
|
|
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: models.Model, filename: str) -> str:
|
|
"""Generate upload path for photos using normalized filenames"""
|
|
# Get the content type and 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', 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=photo.content_type,
|
|
object_id=photo.object_id
|
|
).count()
|
|
next_number = existing_photos + 1
|
|
|
|
# Create normalized filename
|
|
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 = cast(Ride, obj)
|
|
return f"park/{ride.park.slug}/{identifier}/{new_filename}"
|
|
|
|
# For park photos, store directly in park directory
|
|
return f"park/{identifier}/{new_filename}"
|
|
|
|
class Photo(models.Model):
|
|
"""Generic photo model that can be attached to any model"""
|
|
image = models.ImageField(
|
|
upload_to=photo_upload_path, # type: ignore[arg-type]
|
|
max_length=255,
|
|
storage=MediaStorage()
|
|
)
|
|
caption = models.CharField(max_length=255, blank=True)
|
|
alt_text = models.CharField(max_length=255, blank=True)
|
|
is_primary = models.BooleanField(default=False)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
uploaded_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
related_name='uploaded_photos'
|
|
)
|
|
|
|
# Generic foreign key fields
|
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
|
object_id = models.PositiveIntegerField()
|
|
content_object = GenericForeignKey('content_type', 'object_id')
|
|
|
|
class Meta:
|
|
ordering = ['-is_primary', '-created_at']
|
|
indexes = [
|
|
models.Index(fields=['content_type', 'object_id']),
|
|
]
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.content_type} - {self.content_object} - {self.caption or 'No caption'}"
|
|
|
|
def save(self, *args: Any, **kwargs: Any) -> None:
|
|
# Set default caption if not provided
|
|
if not self.caption and self.uploaded_by:
|
|
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:
|
|
Photo.objects.filter(
|
|
content_type=self.content_type,
|
|
object_id=self.object_id,
|
|
is_primary=True
|
|
).exclude(pk=self.pk).update(is_primary=False) # Use pk instead of id
|
|
|
|
super().save(*args, **kwargs)
|