django-cfg 1.2.31__py3-none-any.whl → 1.3.3__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 (264) 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/cleanup_expired_data.py +419 -0
  39. django_cfg/apps/payments/management/commands/currency_stats.py +297 -225
  40. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  41. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  42. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  43. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  44. django_cfg/apps/payments/middleware/__init__.py +3 -1
  45. django_cfg/apps/payments/middleware/api_access.py +329 -222
  46. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  47. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  48. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  49. django_cfg/apps/payments/models/__init__.py +13 -18
  50. django_cfg/apps/payments/models/api_keys.py +121 -43
  51. django_cfg/apps/payments/models/balance.py +153 -115
  52. django_cfg/apps/payments/models/base.py +68 -15
  53. django_cfg/apps/payments/models/currencies.py +172 -148
  54. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  55. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  56. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  57. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  58. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  59. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  60. django_cfg/apps/payments/models/payments.py +235 -285
  61. django_cfg/apps/payments/models/subscriptions.py +257 -177
  62. django_cfg/apps/payments/models/tariffs.py +147 -40
  63. django_cfg/apps/payments/services/__init__.py +209 -56
  64. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  65. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  66. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  67. django_cfg/apps/payments/services/{cache/base.py → cache_service/interfaces.py} +3 -1
  68. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  69. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  70. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  71. django_cfg/apps/payments/services/core/__init__.py +10 -6
  72. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  73. django_cfg/apps/payments/services/core/base.py +166 -0
  74. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  75. django_cfg/apps/payments/services/core/payment_service.py +371 -465
  76. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  77. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  78. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  79. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  80. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  81. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  82. django_cfg/apps/payments/services/providers/base.py +234 -174
  83. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  84. django_cfg/apps/payments/services/providers/registry.py +367 -301
  85. django_cfg/apps/payments/services/types/__init__.py +78 -0
  86. django_cfg/apps/payments/services/types/data.py +177 -0
  87. django_cfg/apps/payments/services/types/requests.py +150 -0
  88. django_cfg/apps/payments/services/types/responses.py +156 -0
  89. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  90. django_cfg/apps/payments/signals/__init__.py +33 -8
  91. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  92. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  93. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  94. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  95. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  96. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  97. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  98. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  99. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  100. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  101. django_cfg/apps/payments/urls.py +45 -48
  102. django_cfg/apps/payments/urls_admin.py +33 -42
  103. django_cfg/apps/payments/views/api/__init__.py +101 -0
  104. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  105. django_cfg/apps/payments/views/api/balances.py +381 -0
  106. django_cfg/apps/payments/views/api/base.py +298 -0
  107. django_cfg/apps/payments/views/api/currencies.py +402 -0
  108. django_cfg/apps/payments/views/api/payments.py +415 -0
  109. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  110. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  111. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  112. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  113. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  114. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  115. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  116. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  117. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  118. django_cfg/config.py +1 -1
  119. django_cfg/core/config.py +40 -4
  120. django_cfg/core/generation.py +25 -4
  121. django_cfg/core/integration/README.md +363 -0
  122. django_cfg/core/integration/__init__.py +47 -0
  123. django_cfg/core/integration/commands_collector.py +239 -0
  124. django_cfg/core/integration/display/__init__.py +15 -0
  125. django_cfg/core/integration/display/base.py +157 -0
  126. django_cfg/core/integration/display/ngrok.py +164 -0
  127. django_cfg/core/integration/display/startup.py +815 -0
  128. django_cfg/core/integration/url_integration.py +123 -0
  129. django_cfg/core/integration/version_checker.py +160 -0
  130. django_cfg/management/commands/auto_generate.py +4 -0
  131. django_cfg/management/commands/check_settings.py +6 -0
  132. django_cfg/management/commands/clear_constance.py +5 -2
  133. django_cfg/management/commands/create_token.py +6 -0
  134. django_cfg/management/commands/list_urls.py +6 -0
  135. django_cfg/management/commands/migrate_all.py +6 -0
  136. django_cfg/management/commands/migrator.py +3 -0
  137. django_cfg/management/commands/rundramatiq.py +6 -0
  138. django_cfg/management/commands/runserver_ngrok.py +51 -29
  139. django_cfg/management/commands/script.py +6 -0
  140. django_cfg/management/commands/show_config.py +12 -2
  141. django_cfg/management/commands/show_urls.py +4 -0
  142. django_cfg/management/commands/superuser.py +6 -0
  143. django_cfg/management/commands/task_clear.py +4 -1
  144. django_cfg/management/commands/task_status.py +3 -1
  145. django_cfg/management/commands/test_email.py +3 -0
  146. django_cfg/management/commands/test_telegram.py +6 -0
  147. django_cfg/management/commands/test_twilio.py +6 -0
  148. django_cfg/management/commands/tree.py +6 -0
  149. django_cfg/management/commands/validate_config.py +155 -149
  150. django_cfg/models/constance.py +31 -11
  151. django_cfg/models/payments.py +175 -492
  152. django_cfg/modules/django_logger.py +160 -146
  153. django_cfg/modules/django_unfold/dashboard.py +64 -16
  154. django_cfg/registry/core.py +1 -0
  155. django_cfg/template_archive/django_sample.zip +0 -0
  156. django_cfg/utils/smart_defaults.py +227 -570
  157. django_cfg/utils/toolkit.py +51 -11
  158. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/METADATA +4 -1
  159. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/RECORD +162 -185
  160. django_cfg/apps/payments/__init__.py +0 -8
  161. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  162. django_cfg/apps/payments/config/module.py +0 -70
  163. django_cfg/apps/payments/config/providers.py +0 -105
  164. django_cfg/apps/payments/config/settings.py +0 -96
  165. django_cfg/apps/payments/config/utils.py +0 -52
  166. django_cfg/apps/payments/decorators.py +0 -291
  167. django_cfg/apps/payments/management/commands/README.md +0 -146
  168. django_cfg/apps/payments/managers/__init__.py +0 -23
  169. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  170. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  171. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  172. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  173. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  174. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  175. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  176. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  177. django_cfg/apps/payments/models/events.py +0 -73
  178. django_cfg/apps/payments/serializers/__init__.py +0 -57
  179. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  180. django_cfg/apps/payments/serializers/balance.py +0 -59
  181. django_cfg/apps/payments/serializers/currencies.py +0 -63
  182. django_cfg/apps/payments/serializers/payments.py +0 -62
  183. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  184. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  185. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  186. django_cfg/apps/payments/services/cache/simple_cache.py +0 -135
  187. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  188. django_cfg/apps/payments/services/internal_types.py +0 -461
  189. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  190. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  191. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  192. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  193. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  194. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  195. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  196. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  197. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  198. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  199. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  200. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  201. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  202. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  203. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  204. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  205. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  206. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  207. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  208. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  209. django_cfg/apps/payments/services/security/__init__.py +0 -34
  210. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  211. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  212. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  213. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  214. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  215. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  216. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  217. django_cfg/apps/payments/tasks/__init__.py +0 -12
  218. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  219. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  220. django_cfg/apps/payments/templates/payments/base.html +0 -182
  221. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  222. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  223. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  224. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  225. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  226. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  227. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  228. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  229. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  230. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  231. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  232. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  233. django_cfg/apps/payments/templates/payments/test.html +0 -213
  234. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  235. django_cfg/apps/payments/utils/__init__.py +0 -43
  236. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  237. django_cfg/apps/payments/utils/config_utils.py +0 -239
  238. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  239. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  240. django_cfg/apps/payments/views/__init__.py +0 -63
  241. django_cfg/apps/payments/views/api_key_views.py +0 -164
  242. django_cfg/apps/payments/views/balance_views.py +0 -75
  243. django_cfg/apps/payments/views/currency_views.py +0 -122
  244. django_cfg/apps/payments/views/payment_views.py +0 -149
  245. django_cfg/apps/payments/views/subscription_views.py +0 -135
  246. django_cfg/apps/payments/views/tariff_views.py +0 -131
  247. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  248. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  249. django_cfg/apps/payments/views/templates/base.py +0 -212
  250. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  251. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  252. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  253. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  254. django_cfg/apps/payments/views/templates/stats.py +0 -244
  255. django_cfg/apps/payments/views/templates/utils.py +0 -181
  256. django_cfg/apps/payments/views/webhook_views.py +0 -266
  257. django_cfg/apps/payments/viewsets.py +0 -66
  258. django_cfg/core/integration.py +0 -160
  259. django_cfg/template_archive/.gitignore +0 -1
  260. django_cfg/template_archive/__init__.py +0 -0
  261. django_cfg/urls.py +0 -33
  262. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  263. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  264. {django_cfg-1.2.31.dist-info → django_cfg-1.3.3.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