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

@@ -4,11 +4,10 @@ from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.conf import settings
from django.test.utils import override_settings
from django.db import models
from datetime import datetime
from PIL import Image
import piexif # type: ignore
import piexif # type: ignore
import io
import shutil
import tempfile
@@ -23,18 +22,19 @@ from parks.models import Park, Company as Operator
User = get_user_model()
logger = logging.getLogger(__name__)
@override_settings(MEDIA_ROOT=tempfile.mkdtemp())
class PhotoModelTests(TestCase):
test_media_root: str
user: models.Model
park: Park
content_type: ContentType
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.test_media_root = settings.MEDIA_ROOT
@classmethod
def tearDownClass(cls) -> None:
try:
@@ -48,7 +48,7 @@ class PhotoModelTests(TestCase):
self.park = self._create_test_park()
self.content_type = ContentType.objects.get_for_model(Park)
self._setup_test_directory()
def tearDown(self) -> None:
self._cleanup_test_directory()
Photo.objects.all().delete()
@@ -57,31 +57,26 @@ class PhotoModelTests(TestCase):
def _create_test_user(self) -> models.Model:
"""Create a test user for the tests"""
return User.objects.create_user(
username='testuser',
password='testpass123'
)
return User.objects.create_user(username="testuser", password="testpass123")
def _create_test_park(self) -> Park:
"""Create a test park for the tests"""
operator = Operator.objects.create(name='Test Operator')
operator = Operator.objects.create(name="Test Operator")
return Park.objects.create(
name='Test Park',
slug='test-park',
operator=operator
name="Test Park", slug="test-park", operator=operator
)
def _setup_test_directory(self) -> None:
"""Set up test directory and clean any existing test files"""
try:
# Clean up any existing test park directory
test_park_dir = os.path.join(settings.MEDIA_ROOT, 'park', 'test-park')
test_park_dir = os.path.join(settings.MEDIA_ROOT, "park", "test-park")
if os.path.exists(test_park_dir):
shutil.rmtree(test_park_dir, ignore_errors=True)
# Create necessary directories
os.makedirs(test_park_dir, exist_ok=True)
except Exception as e:
logger.warning(f"Failed to set up test directory: {e}")
raise
@@ -89,7 +84,7 @@ class PhotoModelTests(TestCase):
def _cleanup_test_directory(self) -> None:
"""Clean up test directories and files"""
try:
test_park_dir = os.path.join(settings.MEDIA_ROOT, 'park', 'test-park')
test_park_dir = os.path.join(settings.MEDIA_ROOT, "park", "test-park")
if os.path.exists(test_park_dir):
shutil.rmtree(test_park_dir, ignore_errors=True)
except Exception as e:
@@ -104,25 +99,29 @@ class PhotoModelTests(TestCase):
finally:
MediaStorage.reset_counters()
def create_test_image_with_exif(self, date_taken: Optional[datetime] = None, filename: str = 'test.jpg') -> SimpleUploadedFile:
def create_test_image_with_exif(
self, date_taken: Optional[datetime] = None, filename: str = "test.jpg"
) -> SimpleUploadedFile:
"""Helper method to create a test image with EXIF data"""
image = Image.new('RGB', (100, 100), color='red')
image = Image.new("RGB", (100, 100), color="red")
image_io = io.BytesIO()
# Save image first without EXIF
image.save(image_io, 'JPEG')
image.save(image_io, "JPEG")
image_io.seek(0)
if date_taken:
# Create EXIF data
exif_dict = {
"0th": {},
"Exif": {
piexif.ExifIFD.DateTimeOriginal: date_taken.strftime("%Y:%m:%d %H:%M:%S").encode()
}
piexif.ExifIFD.DateTimeOriginal: date_taken.strftime(
"%Y:%m:%d %H:%M:%S"
).encode()
},
}
exif_bytes = piexif.dump(exif_dict)
# Insert EXIF into image
image_with_exif = io.BytesIO()
piexif.insert(exif_bytes, image_io.getvalue(), image_with_exif)
@@ -130,24 +129,20 @@ class PhotoModelTests(TestCase):
image_data = image_with_exif.getvalue()
else:
image_data = image_io.getvalue()
return SimpleUploadedFile(
filename,
image_data,
content_type='image/jpeg'
)
return SimpleUploadedFile(filename, image_data, content_type="image/jpeg")
def test_filename_normalization(self) -> None:
"""Test that filenames are properly normalized"""
with self._reset_storage_state():
# Test with various problematic filenames
test_cases = [
('test with spaces.jpg', 'test-park_1.jpg'),
('TEST_UPPER.JPG', 'test-park_2.jpg'),
('special@#chars.jpeg', 'test-park_3.jpg'),
('no-extension', 'test-park_4.jpg'),
('multiple...dots.jpg', 'test-park_5.jpg'),
('très_açaí.jpg', 'test-park_6.jpg'), # Unicode characters
("test with spaces.jpg", "test-park_1.jpg"),
("TEST_UPPER.JPG", "test-park_2.jpg"),
("special@#chars.jpeg", "test-park_3.jpg"),
("no-extension", "test-park_4.jpg"),
("multiple...dots.jpg", "test-park_5.jpg"),
("très_açaí.jpg", "test-park_6.jpg"), # Unicode characters
]
for input_name, expected_suffix in test_cases:
@@ -155,20 +150,22 @@ class PhotoModelTests(TestCase):
image=self.create_test_image_with_exif(filename=input_name),
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk
object_id=self.park.pk,
)
# Check that the filename follows the normalized pattern
self.assertTrue(
photo.image.name.endswith(expected_suffix),
f"Expected filename to end with {expected_suffix}, got {photo.image.name}"
f"Expected filename to end with {expected_suffix}, got {
photo.image.name}",
)
# Verify the path structure
expected_path = f"park/{self.park.slug}/"
self.assertTrue(
photo.image.name.startswith(expected_path),
f"Expected path to start with {expected_path}, got {photo.image.name}"
f"Expected path to start with {expected_path}, got {
photo.image.name}",
)
def test_sequential_filename_numbering(self) -> None:
@@ -180,32 +177,32 @@ class PhotoModelTests(TestCase):
image=self.create_test_image_with_exif(),
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk
object_id=self.park.pk,
)
expected_name = f"park/{self.park.slug}/test-park_{i}.jpg"
self.assertEqual(
photo.image.name,
expected_name,
f"Expected {expected_name}, got {photo.image.name}"
f"Expected {expected_name}, got {photo.image.name}",
)
def test_exif_date_extraction(self) -> None:
"""Test EXIF date extraction from uploaded photos"""
test_date = datetime(2024, 1, 1, 12, 0, 0)
image_file = self.create_test_image_with_exif(test_date)
photo = Photo.objects.create(
image=image_file,
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk
object_id=self.park.pk,
)
if photo.date_taken:
self.assertEqual(
photo.date_taken.strftime("%Y-%m-%d %H:%M:%S"),
test_date.strftime("%Y-%m-%d %H:%M:%S")
test_date.strftime("%Y-%m-%d %H:%M:%S"),
)
else:
self.skipTest("EXIF data extraction not supported in test environment")
@@ -213,14 +210,14 @@ class PhotoModelTests(TestCase):
def test_photo_without_exif(self) -> None:
"""Test photo upload without EXIF data"""
image_file = self.create_test_image_with_exif()
photo = Photo.objects.create(
image=image_file,
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk
object_id=self.park.pk,
)
self.assertIsNone(photo.date_taken)
def test_default_caption(self) -> None:
@@ -229,9 +226,9 @@ class PhotoModelTests(TestCase):
image=self.create_test_image_with_exif(),
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk
object_id=self.park.pk,
)
expected_prefix = f"Uploaded by {cast(Any, self.user).username} on"
self.assertTrue(photo.caption.startswith(expected_prefix))
@@ -242,20 +239,20 @@ class PhotoModelTests(TestCase):
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk,
is_primary=True
is_primary=True,
)
photo2 = Photo.objects.create(
image=self.create_test_image_with_exif(),
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk,
is_primary=True
is_primary=True,
)
photo1.refresh_from_db()
photo2.refresh_from_db()
self.assertFalse(photo1.is_primary)
self.assertTrue(photo2.is_primary)
@@ -267,7 +264,7 @@ class PhotoModelTests(TestCase):
uploaded_by=self.user,
content_type=self.content_type,
object_id=self.park.pk,
date_taken=test_date
date_taken=test_date,
)
self.assertEqual(photo.date_taken, test_date)