feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -28,27 +28,16 @@ class TestFSMTransitionViewHTMX(TestCase):
def setUpTestData(cls):
"""Set up test data for all tests in this class."""
# Create regular user
cls.user = User.objects.create_user(
username="testuser",
email="testuser@example.com",
password="testpass123"
)
cls.user = User.objects.create_user(username="testuser", email="testuser@example.com", password="testpass123")
# Create moderator user
cls.moderator = User.objects.create_user(
username="moderator",
email="moderator@example.com",
password="modpass123",
is_staff=True
username="moderator", email="moderator@example.com", password="modpass123", is_staff=True
)
# Create admin user
cls.admin = User.objects.create_user(
username="admin",
email="admin@example.com",
password="adminpass123",
is_staff=True,
is_superuser=True
username="admin", email="admin@example.com", password="adminpass123", is_staff=True, is_superuser=True
)
def setUp(self):
@@ -76,7 +65,7 @@ class TestFSMTransitionViewHTMX(TestCase):
submission_type="EDIT",
changes={"description": "Test change"},
reason="Integration test",
status="PENDING"
status="PENDING",
)
url = reverse(
@@ -85,15 +74,12 @@ class TestFSMTransitionViewHTMX(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_approved"
}
"transition_name": "transition_to_approved",
},
)
# Make request with HTMX header
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
# Should return 200 OK
self.assertEqual(response.status_code, 200)
@@ -129,7 +115,7 @@ class TestFSMTransitionViewHTMX(TestCase):
submission_type="EDIT",
changes={"description": "Test change non-htmx"},
reason="Integration test non-htmx",
status="PENDING"
status="PENDING",
)
url = reverse(
@@ -138,8 +124,8 @@ class TestFSMTransitionViewHTMX(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_approved"
}
"transition_name": "transition_to_approved",
},
)
# Make request WITHOUT HTMX header
@@ -177,7 +163,7 @@ class TestFSMTransitionViewHTMX(TestCase):
submission_type="EDIT",
changes={"description": "Test partial"},
reason="Partial test",
status="PENDING"
status="PENDING",
)
url = reverse(
@@ -186,14 +172,11 @@ class TestFSMTransitionViewHTMX(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_approved"
}
"transition_name": "transition_to_approved",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
# Response should contain HTML (partial template)
self.assertIn("text/html", response["Content-Type"])
@@ -217,14 +200,11 @@ class TestFSMTransitionViewHTMX(TestCase):
"app_label": "parks",
"model_name": "park",
"pk": park.pk,
"transition_name": "transition_to_closed_temp"
}
"transition_name": "transition_to_closed_temp",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
# Parse HX-Trigger header
trigger_data = json.loads(response["HX-Trigger"])
@@ -249,14 +229,11 @@ class TestFSMTransitionViewHTMX(TestCase):
"app_label": "nonexistent",
"model_name": "fakemodel",
"pk": 1,
"transition_name": "fake_transition"
}
"transition_name": "fake_transition",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
# Should return 404
self.assertEqual(response.status_code, 404)
@@ -282,14 +259,11 @@ class TestFSMTransitionViewHTMX(TestCase):
"app_label": "parks",
"model_name": "park",
"pk": park.pk,
"transition_name": "nonexistent_transition"
}
"transition_name": "nonexistent_transition",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
# Should return 400 Bad Request
self.assertEqual(response.status_code, 400)
@@ -320,7 +294,7 @@ class TestFSMTransitionViewHTMX(TestCase):
submission_type="EDIT",
changes={"description": "Permission test"},
reason="Permission test",
status="PENDING"
status="PENDING",
)
url = reverse(
@@ -329,14 +303,11 @@ class TestFSMTransitionViewHTMX(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_approved"
}
"transition_name": "transition_to_approved",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
# Should return 400 or 403 (permission denied)
self.assertIn(response.status_code, [400, 403])
@@ -355,10 +326,7 @@ class TestFSMTransitionViewParkModel(TestCase):
@classmethod
def setUpTestData(cls):
cls.moderator = User.objects.create_user(
username="mod_park",
email="mod_park@example.com",
password="modpass123",
is_staff=True
username="mod_park", email="mod_park@example.com", password="modpass123", is_staff=True
)
def setUp(self):
@@ -379,14 +347,11 @@ class TestFSMTransitionViewParkModel(TestCase):
"app_label": "parks",
"model_name": "park",
"pk": park.pk,
"transition_name": "transition_to_closed_temp"
}
"transition_name": "transition_to_closed_temp",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -416,14 +381,11 @@ class TestFSMTransitionViewParkModel(TestCase):
"app_label": "parks",
"model_name": "park",
"pk": park.pk,
"transition_name": "transition_to_operating"
}
"transition_name": "transition_to_operating",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -445,14 +407,11 @@ class TestFSMTransitionViewParkModel(TestCase):
"app_label": "parks",
"model_name": "park",
"slug": park.slug,
"transition_name": "transition_to_closed_temp"
}
"transition_name": "transition_to_closed_temp",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -471,10 +430,7 @@ class TestFSMTransitionViewRideModel(TestCase):
@classmethod
def setUpTestData(cls):
cls.moderator = User.objects.create_user(
username="mod_ride",
email="mod_ride@example.com",
password="modpass123",
is_staff=True
username="mod_ride", email="mod_ride@example.com", password="modpass123", is_staff=True
)
def setUp(self):
@@ -495,14 +451,11 @@ class TestFSMTransitionViewRideModel(TestCase):
"app_label": "rides",
"model_name": "ride",
"pk": ride.pk,
"transition_name": "transition_to_closed_temp"
}
"transition_name": "transition_to_closed_temp",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -524,18 +477,10 @@ class TestFSMTransitionViewRideModel(TestCase):
url = reverse(
"core:fsm_transition",
kwargs={
"app_label": "rides",
"model_name": "ride",
"pk": ride.pk,
"transition_name": "transition_to_sbno"
}
kwargs={"app_label": "rides", "model_name": "ride", "pk": ride.pk, "transition_name": "transition_to_sbno"},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -553,17 +498,10 @@ class TestFSMTransitionViewModerationModels(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(
username="submitter",
email="submitter@example.com",
password="testpass123"
)
cls.user = User.objects.create_user(username="submitter", email="submitter@example.com", password="testpass123")
cls.moderator = User.objects.create_user(
username="mod_moderation",
email="mod_moderation@example.com",
password="modpass123",
is_staff=True
username="mod_moderation", email="mod_moderation@example.com", password="modpass123", is_staff=True
)
def setUp(self):
@@ -588,7 +526,7 @@ class TestFSMTransitionViewModerationModels(TestCase):
submission_type="EDIT",
changes={"description": "Approve test"},
reason="Approve test",
status="PENDING"
status="PENDING",
)
url = reverse(
@@ -597,14 +535,11 @@ class TestFSMTransitionViewModerationModels(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_approved"
}
"transition_name": "transition_to_approved",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -633,7 +568,7 @@ class TestFSMTransitionViewModerationModels(TestCase):
submission_type="EDIT",
changes={"description": "Reject test"},
reason="Reject test",
status="PENDING"
status="PENDING",
)
url = reverse(
@@ -642,14 +577,11 @@ class TestFSMTransitionViewModerationModels(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_rejected"
}
"transition_name": "transition_to_rejected",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -678,7 +610,7 @@ class TestFSMTransitionViewModerationModels(TestCase):
submission_type="EDIT",
changes={"description": "Escalate test"},
reason="Escalate test",
status="PENDING"
status="PENDING",
)
url = reverse(
@@ -687,14 +619,11 @@ class TestFSMTransitionViewModerationModels(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_escalated"
}
"transition_name": "transition_to_escalated",
},
)
response = self.client.post(
url,
HTTP_HX_REQUEST="true"
)
response = self.client.post(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
@@ -712,16 +641,11 @@ class TestFSMTransitionViewStateLog(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(
username="submitter_log",
email="submitter_log@example.com",
password="testpass123"
username="submitter_log", email="submitter_log@example.com", password="testpass123"
)
cls.moderator = User.objects.create_user(
username="mod_log",
email="mod_log@example.com",
password="modpass123",
is_staff=True
username="mod_log", email="mod_log@example.com", password="modpass123", is_staff=True
)
def setUp(self):
@@ -748,13 +672,12 @@ class TestFSMTransitionViewStateLog(TestCase):
submission_type="EDIT",
changes={"description": "StateLog test"},
reason="StateLog test",
status="PENDING"
status="PENDING",
)
# Count existing StateLog entries
initial_log_count = StateLog.objects.filter(
content_type=ContentType.objects.get_for_model(EditSubmission),
object_id=submission.pk
content_type=ContentType.objects.get_for_model(EditSubmission), object_id=submission.pk
).count()
url = reverse(
@@ -763,28 +686,23 @@ class TestFSMTransitionViewStateLog(TestCase):
"app_label": "moderation",
"model_name": "editsubmission",
"pk": submission.pk,
"transition_name": "transition_to_approved"
}
"transition_name": "transition_to_approved",
},
)
self.client.post(
url,
HTTP_HX_REQUEST="true"
)
self.client.post(url, HTTP_HX_REQUEST="true")
# Check that a new StateLog entry was created
new_log_count = StateLog.objects.filter(
content_type=ContentType.objects.get_for_model(EditSubmission),
object_id=submission.pk
content_type=ContentType.objects.get_for_model(EditSubmission), object_id=submission.pk
).count()
self.assertEqual(new_log_count, initial_log_count + 1)
# Verify the StateLog entry details
latest_log = StateLog.objects.filter(
content_type=ContentType.objects.get_for_model(EditSubmission),
object_id=submission.pk
).latest('timestamp')
content_type=ContentType.objects.get_for_model(EditSubmission), object_id=submission.pk
).latest("timestamp")
self.assertEqual(latest_log.state, "APPROVED")
self.assertEqual(latest_log.by, self.moderator)

View File

@@ -9,6 +9,7 @@ from datetime import date, timedelta
import pytest
from django.test import TestCase
from django_fsm import TransitionNotAllowed
from tests.factories import (
ParkAreaFactory,
@@ -55,7 +56,7 @@ class TestParkFSMTransitions(TestCase):
user = UserFactory()
# This should fail - can't reopen permanently closed park
with pytest.raises(Exception):
with pytest.raises((TransitionNotAllowed, ValueError)):
park.open(user=user)

View File

@@ -138,9 +138,7 @@ class TestParkReviewWorkflow(TestCase):
ParkReviewFactory(park=park, user=user2, rating=10, is_published=True)
# Calculate average
avg = park.reviews.filter(is_published=True).values_list(
"rating", flat=True
)
avg = park.reviews.filter(is_published=True).values_list("rating", flat=True)
calculated_avg = sum(avg) / len(avg)
assert calculated_avg == 9.0

View File

@@ -31,9 +31,7 @@ class TestParkPhotoUploadWorkflow(TestCase):
@patch("apps.parks.services.media_service.MediaService.process_image")
@patch("apps.parks.services.media_service.MediaService.generate_default_caption")
@patch("apps.parks.services.media_service.MediaService.extract_exif_date")
def test__upload_photo__creates_pending_photo(
self, mock_exif, mock_caption, mock_process, mock_validate
):
def test__upload_photo__creates_pending_photo(self, mock_exif, mock_caption, mock_process, mock_validate):
"""Test uploading photo creates a pending photo."""
mock_validate.return_value = (True, None)
mock_process.return_value = Mock()