project initialization
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
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
This commit is contained in:
403
backend/core/caching/django_integration.py
Normal file
403
backend/core/caching/django_integration.py
Normal file
@@ -0,0 +1,403 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user