django-cfg 1.2.31__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,206 +1,396 @@
1
1
  """
2
- Rate Limiting Middleware.
3
- Implements sliding window rate limiting using Redis.
2
+ Rate Limiting Middleware for the Universal Payment System v2.0.
3
+
4
+ Advanced rate limiting with sliding window algorithm and subscription-aware limits.
4
5
  """
5
6
 
6
- from django_cfg.modules.django_logger import get_logger
7
7
  import time
8
- from typing import Optional
9
- from django.http import JsonResponse, HttpRequest
8
+ import json
9
+ from typing import Optional, Dict, Any, Tuple
10
+ from django.http import JsonResponse, HttpRequest, HttpResponse
10
11
  from django.utils.deprecation import MiddlewareMixin
11
- from django.conf import settings
12
12
  from django.utils import timezone
13
13
  from django.core.cache import cache
14
+ from datetime import datetime, timedelta
14
15
 
15
- logger = get_logger("rate_limiting")
16
+ from ..config.helpers import MiddlewareConfigHelper
17
+ from django_cfg.modules.django_logger import get_logger
18
+
19
+ logger = get_logger("rate_limiting_middleware")
16
20
 
17
21
 
18
22
  class RateLimitingMiddleware(MiddlewareMixin):
19
23
  """
20
- Rate limiting middleware using sliding window algorithm.
24
+ Advanced Rate Limiting Middleware with sliding window algorithm.
21
25
 
22
26
  Features:
23
- - Per-API-key rate limiting
24
- - Per-IP rate limiting (fallback)
25
- - Sliding window algorithm
26
- - Redis-based with circuit breaker
27
- - Configurable limits
27
+ - Sliding window rate limiting
28
+ - Subscription-aware rate limits
29
+ - Per-user and per-IP limiting
30
+ - Burst allowance
31
+ - Rate limit headers
32
+ - Redis-based distributed limiting
33
+ - Graceful degradation
28
34
  """
29
35
 
30
36
  def __init__(self, get_response=None):
31
37
  super().__init__(get_response)
32
38
 
33
- # Default rate limits (can be overridden in settings)
34
- self.default_limits = getattr(settings, 'PAYMENTS_RATE_LIMITS', {
35
- 'per_minute': 60, # 60 requests per minute
36
- 'per_hour': 1000, # 1000 requests per hour
37
- 'per_day': 10000, # 10000 requests per day
38
- })
39
-
40
- # Paths exempt from rate limiting
41
- self.exempt_paths = getattr(settings, 'PAYMENTS_RATE_LIMIT_EXEMPT_PATHS', [
42
- '/admin/',
43
- '/cfg/',
44
- ])
39
+ # Load configuration from django-cfg
40
+ try:
41
+ middleware_config = MiddlewareConfigHelper.get_middleware_config()
42
+
43
+ # Configuration from django-cfg
44
+ self.enabled = middleware_config['rate_limiting_enabled']
45
+ self.default_limits = middleware_config['default_rate_limits']
46
+ self.cache_timeout = middleware_config['cache_timeouts']['rate_limit']
47
+
48
+ # Static defaults
49
+ self.strict_mode = True
50
+ self.burst_allowance = 0.5
51
+ self.window_size = 60 # seconds
52
+ self.window_precision = 10 # sub-windows
53
+ self.exempt_paths = [
54
+ '/api/health/',
55
+ '/admin/',
56
+ '/static/',
57
+ '/media/',
58
+ ]
59
+
60
+ except Exception as e:
61
+ logger.warning(f"Failed to load rate limiting config, using defaults: {e}")
62
+ # Fallback defaults
63
+ self.enabled = True
64
+ self.strict_mode = True
65
+ self.default_limits = {
66
+ 'anonymous': 60,
67
+ 'authenticated': 300,
68
+ 'free': 100,
69
+ 'basic': 500,
70
+ 'premium': 2000,
71
+ 'enterprise': 10000,
72
+ }
73
+ self.burst_allowance = 0.5
74
+ self.window_size = 60
75
+ self.window_precision = 10
76
+ self.exempt_paths = ['/api/health/', '/admin/']
77
+ self.cache_timeout = 300
45
78
 
46
- # Enable/disable rate limiting
47
- self.enabled = getattr(settings, 'PAYMENTS_RATE_LIMITING_ENABLED', True)
79
+ logger.info(f"Rate Limiting Middleware initialized", extra={
80
+ 'enabled': self.enabled,
81
+ 'default_limits': self.default_limits,
82
+ 'window_size': self.window_size,
83
+ 'burst_allowance': self.burst_allowance
84
+ })
48
85
 
49
86
  def process_request(self, request: HttpRequest) -> Optional[JsonResponse]:
50
- """Process request for rate limiting."""
87
+ """
88
+ Process request for rate limiting.
51
89
 
90
+ Returns JsonResponse if rate limit exceeded, None to continue.
91
+ """
52
92
  if not self.enabled:
53
93
  return None
54
94
 
55
- # Skip exempt paths
56
- if self._is_exempt_path(request):
95
+ # Check if path is exempt
96
+ if request.path in self.exempt_paths:
57
97
  return None
58
98
 
59
- # Get rate limiting key (API key or IP)
60
- rate_key = self._get_rate_key(request)
61
- if not rate_key:
62
- return None
99
+ start_time = time.time()
63
100
 
64
- # Get rate limits for this key
65
- limits = self._get_rate_limits(request)
101
+ try:
102
+ # Determine rate limit for this request
103
+ rate_limit, limit_type = self._get_rate_limit(request)
104
+
105
+ # Get client identifier
106
+ client_id = self._get_client_identifier(request)
107
+
108
+ # Check rate limit
109
+ allowed, current_usage, reset_time = self._check_rate_limit(
110
+ client_id, rate_limit, limit_type
111
+ )
112
+
113
+ if not allowed:
114
+ # Rate limit exceeded
115
+ processing_time = (time.time() - start_time) * 1000
116
+
117
+ logger.warning(f"Rate limit exceeded", extra={
118
+ 'client_id': client_id,
119
+ 'limit_type': limit_type,
120
+ 'rate_limit': rate_limit,
121
+ 'current_usage': current_usage,
122
+ 'path': request.path,
123
+ 'processing_time_ms': round(processing_time, 2)
124
+ })
125
+
126
+ return self._create_rate_limit_response(
127
+ rate_limit, current_usage, reset_time
128
+ )
129
+
130
+ # Add rate limit info to request
131
+ request.rate_limit_info = {
132
+ 'limit': rate_limit,
133
+ 'remaining': max(0, rate_limit - current_usage),
134
+ 'reset_time': reset_time,
135
+ 'limit_type': limit_type
136
+ }
137
+
138
+ # Log successful rate limit check
139
+ processing_time = (time.time() - start_time) * 1000
140
+ logger.debug(f"Rate limit check passed", extra={
141
+ 'client_id': client_id,
142
+ 'limit_type': limit_type,
143
+ 'usage': current_usage,
144
+ 'limit': rate_limit,
145
+ 'processing_time_ms': round(processing_time, 2)
146
+ })
147
+
148
+ return None # Continue processing
149
+
150
+ except Exception as e:
151
+ logger.error(f"Rate limiting error", extra={
152
+ 'path': request.path,
153
+ 'error': str(e),
154
+ 'processing_time_ms': round((time.time() - start_time) * 1000, 2)
155
+ })
156
+
157
+ if self.strict_mode:
158
+ return JsonResponse({
159
+ 'success': False,
160
+ 'error': 'Rate limiting service unavailable',
161
+ 'error_code': 'rate_limit_service_error'
162
+ }, status=503)
163
+ else:
164
+ # Graceful degradation: allow request
165
+ return None
166
+
167
+ def _get_rate_limit(self, request: HttpRequest) -> Tuple[int, str]:
168
+ """
169
+ Determine the appropriate rate limit for this request.
66
170
 
67
- # Check each time window
68
- for window, limit in limits.items():
69
- if self._is_rate_limited(rate_key, window, limit):
70
- return self._rate_limit_response(window, limit)
171
+ Returns tuple of (rate_limit, limit_type).
172
+ """
173
+ # Check if user is authenticated via API key
174
+ if hasattr(request, 'api_key') and hasattr(request, 'subscription_access'):
175
+ subscription_access = request.subscription_access
176
+
177
+ if subscription_access.get('allowed'):
178
+ tier = subscription_access.get('tier', 'free')
179
+ rate_limit = self.default_limits.get(tier, self.default_limits['free'])
180
+ return rate_limit, f"subscription_{tier}"
71
181
 
72
- # Record this request
73
- self._record_request(rate_key)
182
+ # Check if user is authenticated (Django auth)
183
+ if hasattr(request, 'user') and request.user.is_authenticated:
184
+ return self.default_limits['authenticated'], 'authenticated'
74
185
 
75
- return None
186
+ # Anonymous user
187
+ return self.default_limits['anonymous'], 'anonymous'
76
188
 
77
- def _is_exempt_path(self, request: HttpRequest) -> bool:
78
- """Check if path is exempt from rate limiting."""
79
- path = request.path
80
- return any(path.startswith(exempt) for exempt in self.exempt_paths)
81
-
82
- def _get_rate_key(self, request: HttpRequest) -> Optional[str]:
83
- """Get rate limiting key (API key preferred, IP as fallback)."""
189
+ def _get_client_identifier(self, request: HttpRequest) -> str:
190
+ """
191
+ Get unique identifier for the client.
84
192
 
85
- # Use API key if available (from previous middleware)
86
- if hasattr(request, 'payment_api_key'):
87
- return f"api_key:{request.payment_api_key.key_value}"
193
+ Prioritizes API key, then user ID, then IP address.
194
+ """
195
+ # Use API key if available (most specific)
196
+ if hasattr(request, 'api_key'):
197
+ return f"api_key:{request.api_key.id}"
88
198
 
89
- # Fallback to IP address
90
- ip = self._get_client_ip(request)
91
- if ip:
92
- return f"ip:{ip}"
199
+ # Use user ID if authenticated
200
+ if hasattr(request, 'user') and request.user.is_authenticated:
201
+ return f"user:{request.user.id}"
93
202
 
94
- return None
203
+ # Fall back to IP address
204
+ return f"ip:{self._get_client_ip(request)}"
95
205
 
96
- def _get_client_ip(self, request: HttpRequest) -> Optional[str]:
97
- """Get client IP address."""
206
+ def _check_rate_limit(self, client_id: str, rate_limit: int, limit_type: str) -> Tuple[bool, int, int]:
207
+ """
208
+ Check rate limit using sliding window algorithm.
98
209
 
99
- # Check for forwarded headers first
100
- forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
101
- if forwarded_for:
102
- return forwarded_for.split(',')[0].strip()
210
+ Returns tuple of (allowed, current_usage, reset_time).
211
+ """
212
+ now = int(time.time())
213
+ window_start = now - self.window_size
103
214
 
104
- # Check for real IP header
105
- real_ip = request.META.get('HTTP_X_REAL_IP')
106
- if real_ip:
107
- return real_ip
215
+ # Cache key for this client's rate limit data
216
+ cache_key = f"rate_limit:{client_id}:{limit_type}"
108
217
 
109
- # Fallback to remote address
110
- return request.META.get('REMOTE_ADDR')
111
-
112
- def _get_rate_limits(self, request: HttpRequest) -> dict:
113
- """Get rate limits for this request."""
218
+ # Get current window data
219
+ window_data = cache.get(cache_key, {})
114
220
 
115
- # Check if API key has custom limits
116
- if hasattr(request, 'payment_api_key'):
117
- api_key = request.payment_api_key
118
-
119
- # Check if user has subscription with custom limits
120
- if hasattr(request, 'payment_subscription'):
121
- subscription = request.payment_subscription
122
- # Custom limits based on subscription tier could be implemented here
123
- # For now, use default limits
124
- pass
125
-
126
- return self.default_limits
127
-
128
- def _is_rate_limited(self, rate_key: str, window: str, limit: int) -> bool:
129
- """Check if rate limit is exceeded for given window."""
221
+ # Clean old entries and count current usage
222
+ current_usage = 0
223
+ cleaned_data = {}
130
224
 
131
- try:
132
- # Get window duration in seconds
133
- window_seconds = self._get_window_seconds(window)
134
- if not window_seconds:
135
- return False
136
-
137
- # Use Redis sliding window
138
- current_time = int(time.time())
139
- window_start = current_time - window_seconds
140
-
141
- # Get request count in window
142
- redis_key = f"rate_limit:{rate_key}:{window}"
143
-
144
- # Simple cache-based rate limiting
145
- count = cache.get(redis_key, 0)
146
-
147
- return count >= limit
148
-
149
- except Exception as e:
150
- logger.error(f"Error checking rate limit: {e}")
151
- # On error, allow request (fail open)
152
- return False
153
-
154
- def _get_window_seconds(self, window: str) -> Optional[int]:
155
- """Convert window name to seconds."""
225
+ for timestamp_str, count in window_data.items():
226
+ timestamp = int(timestamp_str)
227
+ if timestamp > window_start:
228
+ cleaned_data[timestamp_str] = count
229
+ current_usage += count
156
230
 
157
- window_map = {
158
- 'per_minute': 60,
159
- 'per_hour': 3600,
160
- 'per_day': 86400,
161
- }
231
+ # Calculate reset time (next window)
232
+ reset_time = now + self.window_size
162
233
 
163
- return window_map.get(window)
164
-
165
- def _record_request(self, rate_key: str):
166
- """Record request for rate limiting."""
234
+ # Check if we can allow this request
235
+ burst_limit = int(rate_limit * (1 + self.burst_allowance))
167
236
 
168
- try:
169
- current_time = int(time.time())
170
-
171
- # Record for each window
172
- for window in self.default_limits.keys():
173
- window_seconds = self._get_window_seconds(window)
174
- if window_seconds:
175
- redis_key = f"rate_limit:{rate_key}:{window}"
176
-
177
- # Increment request count
178
- current_count = cache.get(redis_key, 0)
179
- cache.set(redis_key, current_count + 1, window_seconds)
180
-
181
- except Exception as e:
182
- logger.error(f"Error recording request: {e}")
237
+ if current_usage >= burst_limit:
238
+ # Hard limit exceeded
239
+ return False, current_usage, reset_time
240
+ elif current_usage >= rate_limit:
241
+ # Soft limit exceeded, but within burst allowance
242
+ logger.info(f"Burst allowance used", extra={
243
+ 'client_id': client_id,
244
+ 'current_usage': current_usage,
245
+ 'rate_limit': rate_limit,
246
+ 'burst_limit': burst_limit
247
+ })
248
+
249
+ # Add current request to window
250
+ current_window = str(now // (self.window_size // self.window_precision) * (self.window_size // self.window_precision))
251
+ cleaned_data[current_window] = cleaned_data.get(current_window, 0) + 1
252
+ current_usage += 1
253
+
254
+ # Save updated window data
255
+ cache.set(cache_key, cleaned_data, timeout=self.cache_timeout)
256
+
257
+ return True, current_usage, reset_time
183
258
 
184
- def _rate_limit_response(self, window: str, limit: int) -> JsonResponse:
185
- """Return rate limit exceeded response."""
259
+ def _get_client_ip(self, request: HttpRequest) -> str:
260
+ """
261
+ Get client IP address from request.
262
+ """
263
+ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
264
+ if x_forwarded_for:
265
+ return x_forwarded_for.split(',')[0].strip()
186
266
 
187
- window_seconds = self._get_window_seconds(window)
188
- retry_after = window_seconds if window_seconds else 60
267
+ x_real_ip = request.META.get('HTTP_X_REAL_IP')
268
+ if x_real_ip:
269
+ return x_real_ip
189
270
 
271
+ return request.META.get('REMOTE_ADDR', 'unknown')
272
+
273
+ def _create_rate_limit_response(self, rate_limit: int, current_usage: int, reset_time: int) -> JsonResponse:
274
+ """
275
+ Create rate limit exceeded response with proper headers.
276
+ """
190
277
  response = JsonResponse({
191
- 'error': {
192
- 'code': 'RATE_LIMIT_EXCEEDED',
193
- 'message': f'Rate limit exceeded: {limit} requests {window}',
194
- 'limit': limit,
195
- 'window': window,
196
- 'retry_after': retry_after,
197
- 'timestamp': timezone.now().isoformat(),
198
- }
278
+ 'success': False,
279
+ 'error': 'Rate limit exceeded',
280
+ 'error_code': 'rate_limit_exceeded',
281
+ 'rate_limit': {
282
+ 'limit': rate_limit,
283
+ 'current': current_usage,
284
+ 'reset_at': reset_time,
285
+ 'reset_in_seconds': max(0, reset_time - int(time.time()))
286
+ },
287
+ 'timestamp': timezone.now().isoformat()
199
288
  }, status=429)
200
289
 
201
290
  # Add rate limit headers
202
- response['X-RateLimit-Limit'] = str(limit)
203
- response['X-RateLimit-Window'] = window
204
- response['Retry-After'] = str(retry_after)
291
+ response['X-RateLimit-Limit'] = str(rate_limit)
292
+ response['X-RateLimit-Remaining'] = '0'
293
+ response['X-RateLimit-Reset'] = str(reset_time)
294
+ response['Retry-After'] = str(max(1, reset_time - int(time.time())))
295
+
296
+ return response
297
+
298
+ def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
299
+ """
300
+ Add rate limit headers to response.
301
+ """
302
+ if hasattr(request, 'rate_limit_info'):
303
+ info = request.rate_limit_info
304
+
305
+ response['X-RateLimit-Limit'] = str(info['limit'])
306
+ response['X-RateLimit-Remaining'] = str(info['remaining'])
307
+ response['X-RateLimit-Reset'] = str(info['reset_time'])
308
+ response['X-RateLimit-Type'] = info['limit_type']
205
309
 
206
310
  return response
311
+
312
+
313
+ class AdaptiveRateLimitingMiddleware(RateLimitingMiddleware):
314
+ """
315
+ Adaptive Rate Limiting Middleware that adjusts limits based on system load.
316
+
317
+ Extends base rate limiting with:
318
+ - System load monitoring
319
+ - Dynamic limit adjustment
320
+ - Circuit breaker pattern
321
+ - Performance-based throttling
322
+ """
323
+
324
+ def __init__(self, get_response=None):
325
+ super().__init__(get_response)
326
+
327
+ # Adaptive configuration
328
+ self.adaptive_enabled = getattr(settings, 'ADAPTIVE_RATE_LIMITING_ENABLED', False)
329
+ self.load_threshold_high = getattr(settings, 'RATE_LIMIT_LOAD_THRESHOLD_HIGH', 0.8)
330
+ self.load_threshold_critical = getattr(settings, 'RATE_LIMIT_LOAD_THRESHOLD_CRITICAL', 0.95)
331
+
332
+ # Performance monitoring
333
+ self.response_time_threshold = getattr(settings, 'RATE_LIMIT_RESPONSE_TIME_THRESHOLD', 1000) # ms
334
+
335
+ logger.info(f"Adaptive Rate Limiting initialized", extra={
336
+ 'adaptive_enabled': self.adaptive_enabled,
337
+ 'load_thresholds': {
338
+ 'high': self.load_threshold_high,
339
+ 'critical': self.load_threshold_critical
340
+ }
341
+ })
342
+
343
+ def _get_rate_limit(self, request: HttpRequest) -> Tuple[int, str]:
344
+ """
345
+ Get adaptive rate limit based on system load and performance.
346
+ """
347
+ base_limit, limit_type = super()._get_rate_limit(request)
348
+
349
+ if not self.adaptive_enabled:
350
+ return base_limit, limit_type
351
+
352
+ # Get system load factor
353
+ load_factor = self._get_system_load_factor()
354
+
355
+ # Adjust rate limit based on load
356
+ if load_factor >= self.load_threshold_critical:
357
+ # Critical load: reduce to 25% of normal
358
+ adjusted_limit = int(base_limit * 0.25)
359
+ limit_type += "_critical"
360
+ elif load_factor >= self.load_threshold_high:
361
+ # High load: reduce to 50% of normal
362
+ adjusted_limit = int(base_limit * 0.5)
363
+ limit_type += "_high_load"
364
+ else:
365
+ # Normal load
366
+ adjusted_limit = base_limit
367
+
368
+ return max(1, adjusted_limit), limit_type # Ensure at least 1 request allowed
369
+
370
+ def _get_system_load_factor(self) -> float:
371
+ """
372
+ Calculate system load factor (0.0 to 1.0).
373
+
374
+ This is a simplified implementation. In production, you might want to:
375
+ - Monitor CPU usage
376
+ - Check database connection pool
377
+ - Monitor Redis performance
378
+ - Check response times
379
+ """
380
+ try:
381
+ # Get average response time from cache
382
+ avg_response_time = cache.get('system_avg_response_time', 100) # ms
383
+
384
+ # Simple load calculation based on response time
385
+ if avg_response_time <= 200:
386
+ return 0.1 # Low load
387
+ elif avg_response_time <= 500:
388
+ return 0.3 # Medium load
389
+ elif avg_response_time <= 1000:
390
+ return 0.6 # High load
391
+ else:
392
+ return 0.9 # Critical load
393
+
394
+ except Exception as e:
395
+ logger.warning(f"Failed to get system load factor: {e}")
396
+ return 0.5 # Default to medium load