mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-01-02 03:27:02 -05:00
feat: Implement initial schema and add various API, service, and management command enhancements across the application.
This commit is contained in:
@@ -8,10 +8,17 @@ Reusable utility functions for testing FSM transitions:
|
||||
- Toast notification verification utilities
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from apps.moderation.models import EditSubmission, PhotoSubmission
|
||||
from apps.parks.models import Park
|
||||
from apps.rides.models import Ride
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Model
|
||||
from django.http import HttpResponse
|
||||
@@ -31,8 +38,8 @@ def create_test_submission(
|
||||
submission_type: str = "EDIT",
|
||||
changes: dict[str, Any] | None = None,
|
||||
reason: str = "Test submission",
|
||||
**kwargs
|
||||
) -> "EditSubmission":
|
||||
**kwargs,
|
||||
) -> EditSubmission:
|
||||
"""
|
||||
Create a test EditSubmission with the given status.
|
||||
|
||||
@@ -54,8 +61,7 @@ def create_test_submission(
|
||||
# Get or create user
|
||||
if user is None:
|
||||
user, _ = User.objects.get_or_create(
|
||||
username="test_submitter",
|
||||
defaults={"email": "test_submitter@example.com"}
|
||||
username="test_submitter", defaults={"email": "test_submitter@example.com"}
|
||||
)
|
||||
user.set_password("testpass123")
|
||||
user.save()
|
||||
@@ -80,18 +86,13 @@ def create_test_submission(
|
||||
changes=changes,
|
||||
reason=reason,
|
||||
status=status,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return submission
|
||||
|
||||
|
||||
def create_test_park(
|
||||
status: str = "OPERATING",
|
||||
name: str | None = None,
|
||||
slug: str | None = None,
|
||||
**kwargs
|
||||
) -> "Park":
|
||||
def create_test_park(status: str = "OPERATING", name: str | None = None, slug: str | None = None, **kwargs) -> Park:
|
||||
"""
|
||||
Create a test Park with the given status.
|
||||
|
||||
@@ -108,29 +109,22 @@ def create_test_park(
|
||||
|
||||
if name is None:
|
||||
import random
|
||||
|
||||
name = f"Test Park {random.randint(1000, 9999)}"
|
||||
|
||||
if slug is None:
|
||||
from django.utils.text import slugify
|
||||
|
||||
slug = slugify(name)
|
||||
|
||||
park = ParkFactory(
|
||||
name=name,
|
||||
slug=slug,
|
||||
status=status,
|
||||
**kwargs
|
||||
)
|
||||
park = ParkFactory(name=name, slug=slug, status=status, **kwargs)
|
||||
|
||||
return park
|
||||
|
||||
|
||||
def create_test_ride(
|
||||
status: str = "OPERATING",
|
||||
name: str | None = None,
|
||||
slug: str | None = None,
|
||||
park: Model | None = None,
|
||||
**kwargs
|
||||
) -> "Ride":
|
||||
status: str = "OPERATING", name: str | None = None, slug: str | None = None, park: Model | None = None, **kwargs
|
||||
) -> Ride:
|
||||
"""
|
||||
Create a test Ride with the given status.
|
||||
|
||||
@@ -148,18 +142,15 @@ def create_test_ride(
|
||||
|
||||
if name is None:
|
||||
import random
|
||||
|
||||
name = f"Test Ride {random.randint(1000, 9999)}"
|
||||
|
||||
if slug is None:
|
||||
from django.utils.text import slugify
|
||||
|
||||
slug = slugify(name)
|
||||
|
||||
ride_kwargs = {
|
||||
"name": name,
|
||||
"slug": slug,
|
||||
"status": status,
|
||||
**kwargs
|
||||
}
|
||||
ride_kwargs = {"name": name, "slug": slug, "status": status, **kwargs}
|
||||
|
||||
if park is not None:
|
||||
ride_kwargs["park"] = park
|
||||
@@ -170,11 +161,8 @@ def create_test_ride(
|
||||
|
||||
|
||||
def create_test_photo_submission(
|
||||
status: str = "PENDING",
|
||||
user: User | None = None,
|
||||
park: Model | None = None,
|
||||
**kwargs
|
||||
) -> "PhotoSubmission":
|
||||
status: str = "PENDING", user: User | None = None, park: Model | None = None, **kwargs
|
||||
) -> PhotoSubmission:
|
||||
"""
|
||||
Create a test PhotoSubmission with the given status.
|
||||
|
||||
@@ -193,8 +181,7 @@ def create_test_photo_submission(
|
||||
# Get or create user
|
||||
if user is None:
|
||||
user, _ = User.objects.get_or_create(
|
||||
username="test_photo_submitter",
|
||||
defaults={"email": "test_photo@example.com"}
|
||||
username="test_photo_submitter", defaults={"email": "test_photo@example.com"}
|
||||
)
|
||||
user.set_password("testpass123")
|
||||
user.save()
|
||||
@@ -210,11 +197,12 @@ def create_test_photo_submission(
|
||||
# Get a photo if available
|
||||
try:
|
||||
from django_cloudflareimages_toolkit.models import CloudflareImage
|
||||
|
||||
photo = CloudflareImage.objects.first()
|
||||
if not photo:
|
||||
raise ValueError("No CloudflareImage available for testing")
|
||||
except ImportError:
|
||||
raise ValueError("CloudflareImage not available")
|
||||
raise ValueError("CloudflareImage not available") from None
|
||||
|
||||
submission = PhotoSubmission.objects.create(
|
||||
user=user,
|
||||
@@ -223,7 +211,7 @@ def create_test_photo_submission(
|
||||
photo=photo,
|
||||
caption="Test photo submission",
|
||||
status=status,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return submission
|
||||
@@ -247,16 +235,11 @@ def assert_status_changed(obj: Model, expected_status: str) -> None:
|
||||
"""
|
||||
obj.refresh_from_db()
|
||||
actual_status = getattr(obj, "status", None)
|
||||
assert actual_status == expected_status, (
|
||||
f"Expected status '{expected_status}', got '{actual_status}'"
|
||||
)
|
||||
assert actual_status == expected_status, f"Expected status '{expected_status}', got '{actual_status}'"
|
||||
|
||||
|
||||
def assert_state_log_created(
|
||||
obj: Model,
|
||||
transition_name: str,
|
||||
user: User | None = None,
|
||||
expected_state: str | None = None
|
||||
obj: Model, transition_name: str, user: User | None = None, expected_state: str | None = None
|
||||
) -> None:
|
||||
"""
|
||||
Assert that a StateLog entry was created for a transition.
|
||||
@@ -274,31 +257,20 @@ def assert_state_log_created(
|
||||
|
||||
content_type = ContentType.objects.get_for_model(obj)
|
||||
|
||||
logs = StateLog.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=obj.pk
|
||||
).order_by('-timestamp')
|
||||
logs = StateLog.objects.filter(content_type=content_type, object_id=obj.pk).order_by("-timestamp")
|
||||
|
||||
assert logs.exists(), "No StateLog entries found for object"
|
||||
|
||||
latest_log = logs.first()
|
||||
|
||||
if expected_state is not None:
|
||||
assert latest_log.state == expected_state, (
|
||||
f"Expected state '{expected_state}' in log, got '{latest_log.state}'"
|
||||
)
|
||||
assert latest_log.state == expected_state, f"Expected state '{expected_state}' in log, got '{latest_log.state}'"
|
||||
|
||||
if user is not None:
|
||||
assert latest_log.by == user, (
|
||||
f"Expected log by user '{user}', got '{latest_log.by}'"
|
||||
)
|
||||
assert latest_log.by == user, f"Expected log by user '{user}', got '{latest_log.by}'"
|
||||
|
||||
|
||||
def assert_toast_triggered(
|
||||
response: HttpResponse,
|
||||
message: str | None = None,
|
||||
toast_type: str = "success"
|
||||
) -> None:
|
||||
def assert_toast_triggered(response: HttpResponse, message: str | None = None, toast_type: str = "success") -> None:
|
||||
"""
|
||||
Assert that the response contains an HX-Trigger header with toast data.
|
||||
|
||||
@@ -316,14 +288,12 @@ def assert_toast_triggered(
|
||||
assert "showToast" in trigger_data, "HX-Trigger missing showToast event"
|
||||
|
||||
toast_data = trigger_data["showToast"]
|
||||
assert toast_data.get("type") == toast_type, (
|
||||
f"Expected toast type '{toast_type}', got '{toast_data.get('type')}'"
|
||||
)
|
||||
assert toast_data.get("type") == toast_type, f"Expected toast type '{toast_type}', got '{toast_data.get('type')}'"
|
||||
|
||||
if message is not None:
|
||||
assert message in toast_data.get("message", ""), (
|
||||
f"Expected '{message}' in toast message, got '{toast_data.get('message')}'"
|
||||
)
|
||||
assert message in toast_data.get(
|
||||
"message", ""
|
||||
), f"Expected '{message}' in toast message, got '{toast_data.get('message')}'"
|
||||
|
||||
|
||||
def assert_no_status_change(obj: Model, original_status: str) -> None:
|
||||
@@ -339,9 +309,9 @@ def assert_no_status_change(obj: Model, original_status: str) -> None:
|
||||
"""
|
||||
obj.refresh_from_db()
|
||||
actual_status = getattr(obj, "status", None)
|
||||
assert actual_status == original_status, (
|
||||
f"Status should not have changed from '{original_status}', but is now '{actual_status}'"
|
||||
)
|
||||
assert (
|
||||
actual_status == original_status
|
||||
), f"Status should not have changed from '{original_status}', but is now '{actual_status}'"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -349,11 +319,7 @@ def assert_no_status_change(obj: Model, original_status: str) -> None:
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def wait_for_htmx_swap(
|
||||
page,
|
||||
target_selector: str,
|
||||
timeout: int = 5000
|
||||
) -> None:
|
||||
def wait_for_htmx_swap(page, target_selector: str, timeout: int = 5000) -> None:
|
||||
"""
|
||||
Wait for an HTMX swap to complete on a target element.
|
||||
|
||||
@@ -370,14 +336,12 @@ def wait_for_htmx_swap(
|
||||
return el && !el.classList.contains('htmx-request');
|
||||
}}
|
||||
""",
|
||||
timeout=timeout
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
|
||||
def verify_transition_buttons_visible(
|
||||
page,
|
||||
transitions: list[str],
|
||||
container_selector: str = "[data-status-actions]"
|
||||
page, transitions: list[str], container_selector: str = "[data-status-actions]"
|
||||
) -> dict[str, bool]:
|
||||
"""
|
||||
Verify which transition buttons are visible on the page.
|
||||
@@ -447,11 +411,7 @@ def wait_for_toast(page, toast_selector: str = "[data-toast]", timeout: int = 50
|
||||
return toast
|
||||
|
||||
|
||||
def wait_for_toast_dismiss(
|
||||
page,
|
||||
toast_selector: str = "[data-toast]",
|
||||
timeout: int = 10000
|
||||
) -> None:
|
||||
def wait_for_toast_dismiss(page, toast_selector: str = "[data-toast]", timeout: int = 10000) -> None:
|
||||
"""
|
||||
Wait for a toast notification to be dismissed.
|
||||
|
||||
@@ -473,6 +433,7 @@ def click_and_confirm(page, button_locator, accept: bool = True) -> None:
|
||||
button_locator: The button locator to click
|
||||
accept: Whether to accept (True) or dismiss (False) the dialog
|
||||
"""
|
||||
|
||||
def handle_dialog(dialog):
|
||||
if accept:
|
||||
dialog.accept()
|
||||
@@ -500,11 +461,7 @@ def make_htmx_post(client, url: str, data: dict | None = None) -> HttpResponse:
|
||||
Returns:
|
||||
HttpResponse from the request
|
||||
"""
|
||||
return client.post(
|
||||
url,
|
||||
data=data or {},
|
||||
HTTP_HX_REQUEST="true"
|
||||
)
|
||||
return client.post(url, data=data or {}, HTTP_HX_REQUEST="true")
|
||||
|
||||
|
||||
def make_htmx_get(client, url: str) -> HttpResponse:
|
||||
@@ -518,19 +475,11 @@ def make_htmx_get(client, url: str) -> HttpResponse:
|
||||
Returns:
|
||||
HttpResponse from the request
|
||||
"""
|
||||
return client.get(
|
||||
url,
|
||||
HTTP_HX_REQUEST="true"
|
||||
)
|
||||
return client.get(url, HTTP_HX_REQUEST="true")
|
||||
|
||||
|
||||
def get_fsm_transition_url(
|
||||
app_label: str,
|
||||
model_name: str,
|
||||
pk: int,
|
||||
transition_name: str,
|
||||
use_slug: bool = False,
|
||||
slug: str | None = None
|
||||
app_label: str, model_name: str, pk: int, transition_name: str, use_slug: bool = False, slug: str | None = None
|
||||
) -> str:
|
||||
"""
|
||||
Generate the URL for an FSM transition.
|
||||
@@ -553,20 +502,10 @@ def get_fsm_transition_url(
|
||||
raise ValueError("slug is required when use_slug is True")
|
||||
return reverse(
|
||||
"core:fsm_transition_by_slug",
|
||||
kwargs={
|
||||
"app_label": app_label,
|
||||
"model_name": model_name,
|
||||
"slug": slug,
|
||||
"transition_name": transition_name
|
||||
}
|
||||
kwargs={"app_label": app_label, "model_name": model_name, "slug": slug, "transition_name": transition_name},
|
||||
)
|
||||
else:
|
||||
return reverse(
|
||||
"core:fsm_transition",
|
||||
kwargs={
|
||||
"app_label": app_label,
|
||||
"model_name": model_name,
|
||||
"pk": pk,
|
||||
"transition_name": transition_name
|
||||
}
|
||||
kwargs={"app_label": app_label, "model_name": model_name, "pk": pk, "transition_name": transition_name},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user