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 def photo_upload_path(instance, filename): """Generate upload path for photos using normalized filenames""" # Get the content type and object content_type = instance.content_type.model obj = instance.content_object # Get object identifier (slug or id) identifier = getattr(obj, 'slug', obj.id) # Get the next available number for this object existing_photos = Photo.objects.filter( content_type=instance.content_type, object_id=instance.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 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) 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, 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): return f"{self.content_type} - {self.content_object} - {self.caption or 'No caption'}" def save(self, *args, **kwargs): # 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')}" # 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(id=self.id).update(is_primary=False) super().save(*args, **kwargs)