django-cfg 1.2.29__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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 +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  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 +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  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 +207 -67
  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 -284
  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 +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  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 +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  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 +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  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 +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  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/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  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 -62
  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 -111
  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 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  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 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  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 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,216 +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
- import logging
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
- from ..services import RateLimitCache
13
+ from django.core.cache import cache
14
+ from datetime import datetime, timedelta
15
+
16
+ from ..config.helpers import MiddlewareConfigHelper
17
+ from django_cfg.modules.django_logger import get_logger
14
18
 
15
- logger = logging.getLogger(__name__)
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
- self.rate_limit_cache = RateLimitCache()
33
38
 
34
- # Default rate limits (can be overridden in settings)
35
- self.default_limits = getattr(settings, 'PAYMENTS_RATE_LIMITS', {
36
- 'per_minute': 60, # 60 requests per minute
37
- 'per_hour': 1000, # 1000 requests per hour
38
- 'per_day': 10000, # 10000 requests per day
39
- })
40
-
41
- # Paths exempt from rate limiting
42
- self.exempt_paths = getattr(settings, 'PAYMENTS_RATE_LIMIT_EXEMPT_PATHS', [
43
- '/admin/',
44
- '/cfg/',
45
- ])
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
46
78
 
47
- # Enable/disable rate limiting
48
- 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
+ })
49
85
 
50
86
  def process_request(self, request: HttpRequest) -> Optional[JsonResponse]:
51
- """Process request for rate limiting."""
87
+ """
88
+ Process request for rate limiting.
52
89
 
90
+ Returns JsonResponse if rate limit exceeded, None to continue.
91
+ """
53
92
  if not self.enabled:
54
93
  return None
55
94
 
56
- # Skip exempt paths
57
- if self._is_exempt_path(request):
95
+ # Check if path is exempt
96
+ if request.path in self.exempt_paths:
58
97
  return None
59
98
 
60
- # Get rate limiting key (API key or IP)
61
- rate_key = self._get_rate_key(request)
62
- if not rate_key:
63
- return None
99
+ start_time = time.time()
64
100
 
65
- # Get rate limits for this key
66
- 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.
67
170
 
68
- # Check each time window
69
- for window, limit in limits.items():
70
- if self._is_rate_limited(rate_key, window, limit):
71
- 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}"
72
181
 
73
- # Record this request
74
- 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'
75
185
 
76
- return None
77
-
78
- def _is_exempt_path(self, request: HttpRequest) -> bool:
79
- """Check if path is exempt from rate limiting."""
80
- path = request.path
81
- return any(path.startswith(exempt) for exempt in self.exempt_paths)
186
+ # Anonymous user
187
+ return self.default_limits['anonymous'], 'anonymous'
82
188
 
83
- def _get_rate_key(self, request: HttpRequest) -> Optional[str]:
84
- """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.
85
192
 
86
- # Use API key if available (from previous middleware)
87
- if hasattr(request, 'payment_api_key'):
88
- 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}"
89
198
 
90
- # Fallback to IP address
91
- ip = self._get_client_ip(request)
92
- if ip:
93
- 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}"
94
202
 
95
- return None
203
+ # Fall back to IP address
204
+ return f"ip:{self._get_client_ip(request)}"
96
205
 
97
- def _get_client_ip(self, request: HttpRequest) -> Optional[str]:
98
- """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.
99
209
 
100
- # Check for forwarded headers first
101
- forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
102
- if forwarded_for:
103
- 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
104
214
 
105
- # Check for real IP header
106
- real_ip = request.META.get('HTTP_X_REAL_IP')
107
- if real_ip:
108
- return real_ip
215
+ # Cache key for this client's rate limit data
216
+ cache_key = f"rate_limit:{client_id}:{limit_type}"
109
217
 
110
- # Fallback to remote address
111
- return request.META.get('REMOTE_ADDR')
112
-
113
- def _get_rate_limits(self, request: HttpRequest) -> dict:
114
- """Get rate limits for this request."""
218
+ # Get current window data
219
+ window_data = cache.get(cache_key, {})
115
220
 
116
- # Check if API key has custom limits
117
- if hasattr(request, 'payment_api_key'):
118
- api_key = request.payment_api_key
119
-
120
- # Check if user has subscription with custom limits
121
- if hasattr(request, 'payment_subscription'):
122
- subscription = request.payment_subscription
123
- # Custom limits based on subscription tier could be implemented here
124
- # For now, use default limits
125
- pass
126
-
127
- return self.default_limits
128
-
129
- def _is_rate_limited(self, rate_key: str, window: str, limit: int) -> bool:
130
- """Check if rate limit is exceeded for given window."""
221
+ # Clean old entries and count current usage
222
+ current_usage = 0
223
+ cleaned_data = {}
131
224
 
132
- try:
133
- # Get window duration in seconds
134
- window_seconds = self._get_window_seconds(window)
135
- if not window_seconds:
136
- return False
137
-
138
- # Use Redis sliding window
139
- current_time = int(time.time())
140
- window_start = current_time - window_seconds
141
-
142
- # Get request count in window
143
- redis_key = f"rate_limit:{rate_key}:{window}"
144
-
145
- # Use Redis sorted set for sliding window
146
- # Remove old entries and count current entries
147
- count = self.redis_service.sliding_window_count(
148
- redis_key,
149
- window_start,
150
- current_time,
151
- window_seconds
152
- )
153
-
154
- return count >= limit
155
-
156
- except Exception as e:
157
- logger.error(f"Error checking rate limit: {e}")
158
- # On error, allow request (fail open)
159
- return False
160
-
161
- def _get_window_seconds(self, window: str) -> Optional[int]:
162
- """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
163
230
 
164
- window_map = {
165
- 'per_minute': 60,
166
- 'per_hour': 3600,
167
- 'per_day': 86400,
168
- }
231
+ # Calculate reset time (next window)
232
+ reset_time = now + self.window_size
169
233
 
170
- return window_map.get(window)
171
-
172
- def _record_request(self, rate_key: str):
173
- """Record request for rate limiting."""
234
+ # Check if we can allow this request
235
+ burst_limit = int(rate_limit * (1 + self.burst_allowance))
174
236
 
175
- try:
176
- current_time = int(time.time())
177
-
178
- # Record for each window
179
- for window in self.default_limits.keys():
180
- window_seconds = self._get_window_seconds(window)
181
- if window_seconds:
182
- redis_key = f"rate_limit:{rate_key}:{window}"
183
-
184
- # Add current timestamp to sorted set
185
- self.redis_service.record_request(
186
- redis_key,
187
- current_time,
188
- window_seconds
189
- )
190
-
191
- except Exception as e:
192
- 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
193
258
 
194
- def _rate_limit_response(self, window: str, limit: int) -> JsonResponse:
195
- """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()
196
266
 
197
- window_seconds = self._get_window_seconds(window)
198
- 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
199
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
+ """
200
277
  response = JsonResponse({
201
- 'error': {
202
- 'code': 'RATE_LIMIT_EXCEEDED',
203
- 'message': f'Rate limit exceeded: {limit} requests {window}',
204
- 'limit': limit,
205
- 'window': window,
206
- 'retry_after': retry_after,
207
- 'timestamp': timezone.now().isoformat(),
208
- }
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()
209
288
  }, status=429)
210
289
 
211
290
  # Add rate limit headers
212
- response['X-RateLimit-Limit'] = str(limit)
213
- response['X-RateLimit-Window'] = window
214
- 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']
215
309
 
216
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