Files
multitenetsaas/backend/core/caching/django_integration.py
AHMET YILMAZ b3fff546e9
Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled
project initialization
2025-10-05 02:37:33 +08:00

403 lines
13 KiB
Python

"""
Django integration for caching strategies.
Provides middleware, decorators, and template tags for easy caching.
"""
import json
import logging
from typing import Any, Dict, List, Optional, Union
from django.core.cache import cache
from django.http import HttpRequest, HttpResponse
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.middleware import AuthenticationMiddleware
from django.utils.deprecation import MiddlewareMixin
from django.template import Library
from django.template.loader import render_to_string
from django.db import connection
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .cache_manager import CacheManager, MalaysianDataCache, QueryCache
from .strategies import (
WriteThroughCache, WriteBehindCache, ReadThroughCache,
RefreshAheadCache, CacheAsidePattern, MultiLevelCache,
MalaysianCacheStrategies, cache_view_response, cache_query_results
)
from .config import CacheConfig
logger = logging.getLogger(__name__)
User = get_user_model()
register = Library()
class TenantCacheMiddleware(MiddlewareMixin):
"""Middleware for tenant-aware caching."""
def __init__(self, get_response):
self.get_response = get_response
self.cache_manager = CacheManager()
self.malaysian_cache = MalaysianDataCache(self.cache_manager)
def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
"""Process request with tenant-aware caching."""
# Add cache manager to request
request.cache_manager = self.cache_manager
request.malaysian_cache = self.malaysian_cache
# Cache tenant-specific data
if hasattr(request, 'tenant') and request.tenant:
tenant_key = f"tenant_data_{request.tenant.id}"
request.tenant_cache = self.cache_manager.get(tenant_key, {})
else:
request.tenant_cache = {}
return None
def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
"""Process response with caching."""
# Add cache headers
response['X-Cache-Tenant'] = getattr(request, 'tenant', {}).get('schema_name', 'public')
response['X-Cache-Status'] = 'MISS' # Will be updated by cache middleware
return response
class CacheMiddleware(MiddlewareMixin):
"""Advanced caching middleware."""
def __init__(self, get_response):
self.get_response = get_response
self.cache_manager = CacheManager()
self.cache_aside = CacheAsidePattern(self.cache_manager)
# Define cacheable paths and conditions
self.cacheable_paths = getattr(settings, 'CACHEABLE_PATHS', [
'/api/products/',
'/api/categories/',
'/api/static-data/',
])
self.non_cacheable_paths = getattr(settings, 'NON_CACHEABLE_PATHS', [
'/api/auth/',
'/api/admin/',
'/api/cart/',
'/api/orders/',
])
def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
"""Process request with caching."""
if self._should_bypass_cache(request):
return None
cache_key = self._generate_cache_key(request)
cached_response = self.cache_manager.get(cache_key)
if cached_response:
response = HttpResponse(cached_response['content'])
response['X-Cache-Status'] = 'HIT'
response['Content-Type'] = cached_response.get('content_type', 'application/json')
return response
return None
def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
"""Process response with caching."""
if self._should_bypass_cache(request) or getattr(response, '_cache_exempt', False):
response['X-Cache-Status'] = 'BYPASS'
return response
if self._should_cache_response(request, response):
cache_key = self._generate_cache_key(request)
cache_data = {
'content': response.content,
'content_type': response.get('Content-Type', 'application/json'),
'status_code': response.status_code,
}
timeout = self._get_cache_timeout(request)
self.cache_manager.set(cache_key, cache_data, timeout)
response['X-Cache-Status'] = 'MISS'
return response
def _should_bypass_cache(self, request: HttpRequest) -> bool:
"""Check if request should bypass cache."""
# Never cache authenticated user requests by default
if request.user.is_authenticated:
if getattr(settings, 'CACHE_AUTHENTICATED_REQUESTS', False):
return False
return True
# Check method
if request.method not in ['GET', 'HEAD']:
return True
# Check paths
for path in self.non_cacheable_paths:
if request.path.startswith(path):
return True
return False
def _should_cache_response(self, request: HttpRequest, response: HttpResponse) -> bool:
"""Check if response should be cached."""
if response.status_code != 200:
return False
# Check cacheable paths
for path in self.cacheable_paths:
if request.path.startswith(path):
return True
return False
def _generate_cache_key(self, request: HttpRequest) -> str:
"""Generate cache key for request."""
key_parts = [
request.path,
request.method,
]
if request.GET:
key_parts.append(str(sorted(request.GET.items())))
# Add user info if authenticated
if request.user.is_authenticated:
key_parts.append(f"user_{request.user.id}")
# Add tenant info
if hasattr(request, 'tenant'):
key_parts.append(f"tenant_{request.tenant.id}")
key = "|".join(key_parts)
return f"view_cache_{hash(key)}"
def _get_cache_timeout(self, request: HttpRequest) -> int:
"""Get cache timeout for request."""
# Different timeouts for different paths
if request.path.startswith('/api/static-data/'):
return 3600 # 1 hour for static data
elif request.path.startswith('/api/products/'):
return 1800 # 30 minutes for products
else:
return 300 # 5 minutes default
class DatabaseCacheMiddleware(MiddlewareMixin):
"""Middleware for database query caching."""
def __init__(self, get_response):
self.get_response = get_response
self.cache_manager = CacheManager()
self.query_cache = QueryCache(self.cache_manager)
self.queries_executed = []
self.cache_hits = 0
def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
"""Initialize query tracking."""
self.queries_executed = []
self.cache_hits = 0
# Add query cache to request
request.query_cache = self.query_cache
return None
def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
"""Add query cache statistics to response."""
response['X-Cache-Queries'] = str(len(self.queries_executed))
response['X-Cache-Query-Hits'] = str(self.cache_hits)
return response
class MalaysianCacheMiddleware(MiddlewareMixin):
"""Middleware for Malaysian-specific caching."""
def __init__(self, get_response):
self.get_response = get_response
self.cache_manager = CacheManager()
self.malaysian_cache = MalaysianDataCache(self.cache_manager)
self.malaysian_strategies = MalaysianCacheStrategies(self.cache_manager)
def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
"""Add Malaysian cache to request."""
request.malaysian_cache = self.malaysian_cache
request.malaysian_strategies = self.malaysian_strategies
return None
# Template Tags
@register.simple_tag
def get_cached_postcode(postcode: str) -> str:
"""Get cached postcode data in template."""
cache_manager = CacheManager()
malaysian_cache = MalaysianDataCache(cache_manager)
data = malaysian_cache.get_cached_postcode_data(postcode)
if data:
return f"{data.get('city', 'Unknown')}, {data.get('state', 'Unknown')}"
return "Unknown location"
@register.simple_tag
def get_cached_sst_rate(state: str, category: str = "standard") -> str:
"""Get cached SST rate in template."""
cache_manager = CacheManager()
malaysian_cache = MalaysianDataCache(cache_manager)
rate = malaysian_cache.get_cached_sst_rate(state, category)
if rate is not None:
return f"{rate * 100:.0f}%"
return "Rate not available"
@register.simple_tag
def get_user_cache_info(user) -> str:
"""Get user cache information."""
if not user or not user.is_authenticated:
return "Anonymous"
cache_manager = CacheManager()
key = f"user_profile_{user.id}"
cached_data = cache_manager.get(key)
if cached_data:
return f"Cached user data available for {user.username}"
return f"No cached data for {user.username}"
# API Views
@api_view(['GET'])
def cache_stats(request):
"""Get cache statistics."""
if not request.user.is_staff:
return Response({"error": "Unauthorized"}, status=403)
cache_manager = CacheManager()
stats = cache_manager.get_cache_stats()
return Response(stats)
@api_view(['POST'])
def clear_cache(request):
"""Clear cache."""
if not request.user.is_staff:
return Response({"error": "Unauthorized"}, status=403)
cache_manager = CacheManager()
# Clear specific cache keys
cache_type = request.data.get('type', 'all')
if cache_type == 'tenant':
tenant_id = request.data.get('tenant_id')
success = cache_manager.clear_tenant_cache(tenant_id)
elif cache_type == 'malaysian':
# Clear Malaysian data cache
success = cache_manager.clear_tenant_cache()
else:
# Clear all cache
success = cache_manager.clear_tenant_cache()
if success:
return Response({"message": f"Cache cleared successfully"})
else:
return Response({"error": "Failed to clear cache"}, status=500)
@api_view(['GET'])
def warm_cache(request):
"""Warm cache with frequently accessed data."""
if not request.user.is_staff:
return Response({"error": "Unauthorized"}, status=403)
from .cache_manager import cache_warmer
# Warm Malaysian data
warmed = cache_warmer.warm_malaysian_data()
# Warm user data if specified
user_ids = request.GET.getlist('user_ids')
if user_ids:
user_ids = [int(uid) for uid in user_ids]
warmed_users = cache_warmer.warm_user_data(user_ids)
warmed['users'] = warmed_users
return Response({
"message": "Cache warming completed",
"warmed_items": warmed
})
# Django Settings Integration
def get_cache_config() -> Dict[str, Any]:
"""Get cache configuration for Django settings."""
return {
'CACHES': {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': getattr(settings, 'REDIS_URL', 'redis://127.0.0.1:6379/1'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
'KEY_PREFIX': 'malaysian_sme_',
},
'locmem': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
},
},
'CACHE_MIDDLEWARE_ALIAS': 'default',
'CACHE_MIDDLEWARE_SECONDS': 300,
'CACHE_MIDDLEWARE_KEY_PREFIX': 'malaysian_sme_',
}
# Signal Handlers
def invalidate_user_cache(sender, instance, **kwargs):
"""Invalidate cache when user is updated."""
cache_manager = CacheManager()
cache_key = f"user_profile_{instance.id}"
cache_manager.delete(cache_key)
# Clear tenant cache if user is tenant owner
if hasattr(instance, 'owned_tenants'):
for tenant in instance.owned_tenants.all():
cache_manager.clear_tenant_cache(tenant.id)
def invalidate_model_cache(sender, instance, **kwargs):
"""Invalidate cache when model is updated."""
cache_manager = CacheManager()
query_cache = QueryCache(cache_manager)
model_name = instance.__class__.__name__.lower()
query_cache.invalidate_model_cache(model_name)
# Django Admin Integration
class CacheAdminMixin:
"""Mixin for Django admin cache management."""
def save_model(self, request, obj, form, change):
"""Override save to invalidate cache."""
super().save_model(request, obj, form, change)
# Invalidate cache
cache_manager = CacheManager()
model_name = obj.__class__.__name__.lower()
query_cache = QueryCache(cache_manager)
query_cache.invalidate_model_cache(model_name)
def delete_model(self, request, obj):
"""Override delete to invalidate cache."""
cache_manager = CacheManager()
model_name = obj.__class__.__name__.lower()
query_cache = QueryCache(cache_manager)
query_cache.invalidate_model_cache(model_name)
super().delete_model(request, obj)