mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-27 08:27:03 -05:00
feat: Add blog, media, and support apps, implement ride credits and image API, and remove toplist feature.
This commit is contained in:
@@ -11,6 +11,7 @@ from django.http import HttpRequest, HttpResponseBase
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
from django.views import View
|
||||
from rest_framework.response import Response as DRFResponse
|
||||
from apps.core.services.enhanced_cache_service import EnhancedCacheService
|
||||
import logging
|
||||
|
||||
@@ -81,6 +82,14 @@ def cache_api_response(
|
||||
"cache_hit": True,
|
||||
},
|
||||
)
|
||||
|
||||
# If cached data is our dict format for DRF responses, reconstruct it
|
||||
if isinstance(cached_response, dict) and '__drf_data__' in cached_response:
|
||||
return DRFResponse(
|
||||
data=cached_response['__drf_data__'],
|
||||
status=cached_response.get('status', 200)
|
||||
)
|
||||
|
||||
return cached_response
|
||||
|
||||
# Execute view and cache result
|
||||
@@ -90,8 +99,18 @@ def cache_api_response(
|
||||
|
||||
# Only cache successful responses
|
||||
if hasattr(response, "status_code") and response.status_code == 200:
|
||||
# For DRF responses, we must cache the data, not the response object
|
||||
# because the response object is not rendered yet and cannot be pickled
|
||||
if hasattr(response, 'data'):
|
||||
cache_payload = {
|
||||
'__drf_data__': response.data,
|
||||
'status': response.status_code
|
||||
}
|
||||
else:
|
||||
cache_payload = response
|
||||
|
||||
getattr(cache_service, cache_backend + "_cache").set(
|
||||
cache_key, response, timeout
|
||||
cache_key, cache_payload, timeout
|
||||
)
|
||||
logger.debug(
|
||||
f"Cached API response for view {view_func.__name__}",
|
||||
|
||||
@@ -16,3 +16,13 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
|
||||
if hasattr(obj, 'user'):
|
||||
return obj.user == request.user
|
||||
return False
|
||||
|
||||
class IsStaffOrReadOnly(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission to only allow staff to edit it.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
return request.user and request.user.is_staff
|
||||
|
||||
@@ -229,7 +229,6 @@ class EntityFuzzyMatcher:
|
||||
parks = Park.objects.filter(
|
||||
Q(name__icontains=query)
|
||||
| Q(slug__icontains=query.lower().replace(" ", "-"))
|
||||
| Q(former_names__icontains=query)
|
||||
)[: self.MAX_CANDIDATES]
|
||||
|
||||
for park in parks:
|
||||
@@ -249,7 +248,6 @@ class EntityFuzzyMatcher:
|
||||
rides = Ride.objects.select_related("park").filter(
|
||||
Q(name__icontains=query)
|
||||
| Q(slug__icontains=query.lower().replace(" ", "-"))
|
||||
| Q(former_names__icontains=query)
|
||||
| Q(park__name__icontains=query)
|
||||
)[: self.MAX_CANDIDATES]
|
||||
|
||||
|
||||
54
backend/apps/core/tests/test_history.py
Normal file
54
backend/apps/core/tests/test_history.py
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from apps.parks.models import Park, Company
|
||||
import pghistory
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestTrackedModel:
|
||||
"""
|
||||
Tests for the TrackedModel base class and pghistory integration.
|
||||
"""
|
||||
|
||||
def test_create_history_tracking(self):
|
||||
"""Test that creating a model instance creates a history event."""
|
||||
user = User.objects.create_user(username="testuser", password="password")
|
||||
company = Company.objects.create(name="Test Operator", roles=["OPERATOR"])
|
||||
|
||||
with pghistory.context(user=user.id):
|
||||
park = Park.objects.create(
|
||||
name="History Test Park",
|
||||
description="Testing history",
|
||||
operating_season="Summer",
|
||||
operator=company
|
||||
)
|
||||
|
||||
# Verify history using the helper method from TrackedModel
|
||||
events = park.get_history()
|
||||
assert events.count() == 1
|
||||
event = events.first()
|
||||
assert event.pgh_obj_id == park.pk
|
||||
|
||||
# Verify context was captured
|
||||
# The middleware isn't running here, so we used pghistory.context explicitly
|
||||
# But pghistory.context stores data in pgh_context field if configured?
|
||||
# Let's check if the event has pgh_context
|
||||
assert event.pgh_context["user"] == user.id
|
||||
|
||||
def test_update_tracking(self):
|
||||
company = Company.objects.create(name="Test Operator 2", roles=["OPERATOR"])
|
||||
park = Park.objects.create(name="Original", operator=company)
|
||||
|
||||
# Initial create event
|
||||
assert park.get_history().count() == 1
|
||||
|
||||
# Update
|
||||
park.name = "Updated"
|
||||
park.save()
|
||||
|
||||
assert park.get_history().count() == 2
|
||||
latest = park.get_history().first() # Ordered by -pgh_created_at
|
||||
assert latest.name == "Updated"
|
||||
|
||||
53
backend/apps/core/utils/cloudflare.py
Normal file
53
backend/apps/core/utils/cloudflare.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_direct_upload_url(user_id=None):
|
||||
"""
|
||||
Generates a direct upload URL for Cloudflare Images.
|
||||
|
||||
Args:
|
||||
user_id (str, optional): The user ID to associate with the upload.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing 'id' and 'uploadURL'.
|
||||
|
||||
Raises:
|
||||
ImproperlyConfigured: If Cloudflare settings are missing.
|
||||
requests.RequestException: If the Cloudflare API request fails.
|
||||
"""
|
||||
account_id = getattr(settings, 'CLOUDFLARE_IMAGES_ACCOUNT_ID', None)
|
||||
api_token = getattr(settings, 'CLOUDFLARE_IMAGES_API_TOKEN', None)
|
||||
|
||||
if not account_id or not api_token:
|
||||
raise ImproperlyConfigured(
|
||||
"CLOUDFLARE_IMAGES_ACCOUNT_ID and CLOUDFLARE_IMAGES_API_TOKEN must be set."
|
||||
)
|
||||
|
||||
url = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v2/direct_upload"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_token}",
|
||||
}
|
||||
|
||||
data = {
|
||||
"requireSignedURLs": "false",
|
||||
}
|
||||
|
||||
if user_id:
|
||||
data["metadata"] = f'{{"user_id": "{user_id}"}}'
|
||||
|
||||
response = requests.post(url, headers=headers, data=data)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
|
||||
if not result.get("success"):
|
||||
error_msg = result.get("errors", [{"message": "Unknown error"}])[0].get("message")
|
||||
logger.error(f"Cloudflare Direct Upload Error: {error_msg}")
|
||||
raise requests.RequestException(f"Cloudflare Error: {error_msg}")
|
||||
|
||||
return result.get("result", {})
|
||||
Reference in New Issue
Block a user