Refactor test utilities and enhance ASGI settings

- Cleaned up and standardized assertions in ApiTestMixin for API response validation.
- Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE.
- Removed unused imports and improved formatting in settings.py.
- Refactored URL patterns in urls.py for better readability and organization.
- Enhanced view functions in views.py for consistency and clarity.
- Added .flake8 configuration for linting and style enforcement.
- Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
pacnpal
2025-08-20 19:51:59 -04:00
parent 69c07d1381
commit 66ed4347a9
230 changed files with 15094 additions and 11578 deletions

View File

@@ -1,12 +1,9 @@
from typing import Any, Optional, Union, cast
from typing import Any, Optional, 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 PIL import Image, ExifTags
from PIL.ExifTags import TAGS
from datetime import datetime
from .storage import MediaStorage
from rides.models import Ride
@@ -14,39 +11,42 @@ from django.utils import timezone
from core.history import TrackedModel
import pghistory
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)
identifier = getattr(obj, "slug", None)
if identifier is None:
identifier = obj.pk # Use pk instead of id as it's guaranteed to exist
# Create normalized filename - always use .jpg extension
base_filename = f"{identifier}.jpg"
# If it's a ride photo, store it under the park's directory
if content_type == 'ride':
if content_type == "ride":
ride = cast(Ride, obj)
return f"park/{ride.park.slug}/{identifier}/{base_filename}"
# For park photos, store directly in park directory
return f"park/{identifier}/{base_filename}"
@pghistory.track()
class Photo(TrackedModel):
"""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()
storage=MediaStorage(),
)
caption = models.CharField(max_length=255, blank=True)
alt_text = models.CharField(max_length=255, blank=True)
@@ -59,20 +59,20 @@ class Photo(TrackedModel):
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='uploaded_photos'
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')
content_object = GenericForeignKey("content_type", "object_id")
class Meta:
ordering = ['-is_primary', '-created_at']
ordering = ["-is_primary", "-created_at"]
indexes = [
models.Index(fields=['content_type', 'object_id']),
models.Index(fields=["content_type", "object_id"]),
]
def __str__(self) -> str:
return f"{self.content_type} - {self.content_object} - {self.caption or 'No caption'}"
@@ -84,15 +84,16 @@ class Photo(TrackedModel):
if exif:
# Find the DateTime tag ID
for tag_id in ExifTags.TAGS:
if ExifTags.TAGS[tag_id] == 'DateTimeOriginal':
if ExifTags.TAGS[tag_id] == "DateTimeOriginal":
if tag_id in exif:
# EXIF dates are typically in format: '2024:02:15 14:30:00'
# EXIF dates are typically in format:
# '2024:02:15 14:30:00'
date_str = exif[tag_id]
return datetime.strptime(date_str, '%Y:%m:%d %H:%M:%S')
return datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S")
return None
except Exception:
return None
def save(self, *args: Any, **kwargs: Any) -> None:
# Extract EXIF date if this is a new photo
if not self.pk and not self.date_taken:
@@ -101,14 +102,18 @@ class Photo(TrackedModel):
# 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')}"
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
is_primary=True,
).exclude(pk=self.pk).update(
is_primary=False
) # Use pk instead of id
super().save(*args, **kwargs)