mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 17:11:13 -05:00
feat: Implement avatar upload system with Cloudflare integration
- Added migration to transition avatar data from CloudflareImageField to ForeignKey structure in UserProfile. - Fixed UserProfileEvent avatar field to align with new avatar structure. - Created serializers for social authentication, including connected and available providers. - Developed request logging middleware for comprehensive request/response logging. - Updated moderation and parks migrations to remove outdated triggers and adjust foreign key relationships. - Enhanced rides migrations to ensure proper handling of image uploads and triggers. - Introduced a test script for the 3-step avatar upload process, ensuring functionality with Cloudflare. - Documented the fix for avatar upload issues, detailing root cause, implementation, and verification steps. - Implemented automatic deletion of Cloudflare images upon avatar, park, and ride photo changes or removals.
This commit is contained in:
@@ -54,7 +54,7 @@ class Command(BaseCommand):
|
||||
)
|
||||
|
||||
# Determine which server command to use
|
||||
server_command = self.get_server_command(options)
|
||||
self.get_server_command(options)
|
||||
|
||||
# Start the server
|
||||
self.stdout.write("")
|
||||
|
||||
@@ -266,13 +266,13 @@ class Command(BaseCommand):
|
||||
trending_parks = trending_service.get_trending_content(
|
||||
content_type="parks", limit=3
|
||||
)
|
||||
trending_rides = trending_service.get_trending_content(
|
||||
trending_service.get_trending_content(
|
||||
content_type="rides", limit=3
|
||||
)
|
||||
|
||||
# Test new content format
|
||||
new_parks = trending_service.get_new_content(content_type="parks", limit=3)
|
||||
new_rides = trending_service.get_new_content(content_type="rides", limit=3)
|
||||
trending_service.get_new_content(content_type="rides", limit=3)
|
||||
|
||||
# Verify trending data structure
|
||||
if trending_parks:
|
||||
|
||||
138
backend/apps/core/middleware/request_logging.py
Normal file
138
backend/apps/core/middleware/request_logging.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
Request logging middleware for comprehensive request/response logging.
|
||||
Logs all HTTP requests with detailed data for debugging and monitoring.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import json
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
logger = logging.getLogger('request_logging')
|
||||
|
||||
|
||||
class RequestLoggingMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
Middleware to log all HTTP requests with method, path, and response code.
|
||||
Includes detailed request/response data logging for all requests.
|
||||
"""
|
||||
|
||||
# Paths to exclude from detailed logging (e.g., static files, health checks)
|
||||
EXCLUDE_DETAILED_LOGGING_PATHS = [
|
||||
'/static/',
|
||||
'/media/',
|
||||
'/favicon.ico',
|
||||
'/health/',
|
||||
'/admin/jsi18n/',
|
||||
]
|
||||
|
||||
def _should_log_detailed(self, request):
|
||||
"""Determine if detailed logging should be enabled for this request."""
|
||||
return not any(
|
||||
path in request.path for path in self.EXCLUDE_DETAILED_LOGGING_PATHS)
|
||||
|
||||
def process_request(self, request):
|
||||
"""Store request start time and capture request data for detailed logging."""
|
||||
request._start_time = time.time()
|
||||
|
||||
# Enable detailed logging for all requests except excluded paths
|
||||
should_log_detailed = self._should_log_detailed(request)
|
||||
request._log_request_data = should_log_detailed
|
||||
|
||||
if should_log_detailed:
|
||||
try:
|
||||
# Log request data
|
||||
request_data = {}
|
||||
if hasattr(request, 'data') and request.data:
|
||||
request_data = dict(request.data)
|
||||
elif request.body:
|
||||
try:
|
||||
request_data = json.loads(request.body.decode('utf-8'))
|
||||
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||
request_data = {'body': str(request.body)[
|
||||
:200] + '...' if len(str(request.body)) > 200 else str(request.body)}
|
||||
|
||||
# Log query parameters
|
||||
query_params = dict(request.GET) if request.GET else {}
|
||||
|
||||
logger.info(f"REQUEST DATA for {request.method} {request.path}:")
|
||||
if request_data:
|
||||
logger.info(f" Body: {self._safe_log_data(request_data)}")
|
||||
if query_params:
|
||||
logger.info(f" Query: {query_params}")
|
||||
if hasattr(request, 'user') and request.user.is_authenticated:
|
||||
logger.info(
|
||||
f" User: {request.user.username} (ID: {request.user.id})")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to log request data: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""Log request details after response is generated."""
|
||||
try:
|
||||
# Calculate request duration
|
||||
duration = 0
|
||||
if hasattr(request, '_start_time'):
|
||||
duration = time.time() - request._start_time
|
||||
|
||||
# Basic request logging
|
||||
logger.info(
|
||||
f"{request.method} {request.get_full_path()} -> {response.status_code} "
|
||||
f"({duration:.3f}s)"
|
||||
)
|
||||
|
||||
# Detailed response logging for specific endpoints
|
||||
if getattr(request, '_log_request_data', False):
|
||||
try:
|
||||
# Log response data
|
||||
if hasattr(response, 'data'):
|
||||
logger.info(
|
||||
f"RESPONSE DATA for {request.method} {request.path}:")
|
||||
logger.info(f" Status: {response.status_code}")
|
||||
logger.info(f" Data: {self._safe_log_data(response.data)}")
|
||||
elif hasattr(response, 'content'):
|
||||
try:
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
logger.info(
|
||||
f"RESPONSE DATA for {request.method} {request.path}:")
|
||||
logger.info(f" Status: {response.status_code}")
|
||||
logger.info(f" Content: {self._safe_log_data(content)}")
|
||||
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||
logger.info(
|
||||
f"RESPONSE DATA for {request.method} {request.path}:")
|
||||
logger.info(f" Status: {response.status_code}")
|
||||
logger.info(f" Content: {str(response.content)[:200]}...")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to log response data: {e}")
|
||||
|
||||
except Exception:
|
||||
# Don't let logging errors break the request
|
||||
pass
|
||||
|
||||
return response
|
||||
|
||||
def _safe_log_data(self, data):
|
||||
"""Safely log data, truncating if too large and masking sensitive fields."""
|
||||
try:
|
||||
# Convert to string representation
|
||||
if isinstance(data, dict):
|
||||
# Mask sensitive fields
|
||||
safe_data = {}
|
||||
for key, value in data.items():
|
||||
if any(sensitive in key.lower() for sensitive in ['password', 'token', 'secret', 'key']):
|
||||
safe_data[key] = '***MASKED***'
|
||||
else:
|
||||
safe_data[key] = value
|
||||
data_str = json.dumps(safe_data, indent=2, default=str)
|
||||
else:
|
||||
data_str = json.dumps(data, indent=2, default=str)
|
||||
|
||||
# Truncate if too long
|
||||
if len(data_str) > 1000:
|
||||
return data_str[:1000] + '...[TRUNCATED]'
|
||||
return data_str
|
||||
except Exception:
|
||||
return str(data)[:500] + '...[ERROR_LOGGING]'
|
||||
5
backend/apps/core/patches/__init__.py
Normal file
5
backend/apps/core/patches/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Patches for third-party packages.
|
||||
"""
|
||||
|
||||
# No patches currently applied
|
||||
Reference in New Issue
Block a user