Files
thrillwiki_django_no_react/media/models.py
pacnpal 01e0a609d2 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.
2024-11-04 05:25:53 +00:00

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)