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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/health/views.py +4 -2
- django_cfg/apps/knowbase/config/settings.py +16 -15
- django_cfg/apps/payments/README.md +326 -0
- django_cfg/apps/payments/admin/__init__.py +20 -9
- django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
- django_cfg/apps/payments/admin/balance_admin.py +592 -297
- django_cfg/apps/payments/admin/currencies_admin.py +600 -108
- django_cfg/apps/payments/admin/filters.py +306 -199
- django_cfg/apps/payments/admin/payments_admin.py +470 -64
- django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
- django_cfg/apps/payments/admin_interface/__init__.py +18 -0
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
- django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
- django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
- django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
- django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
- django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
- django_cfg/apps/payments/apps.py +34 -9
- django_cfg/apps/payments/config/__init__.py +28 -51
- django_cfg/apps/payments/config/constance/__init__.py +22 -0
- django_cfg/apps/payments/config/constance/config_service.py +123 -0
- django_cfg/apps/payments/config/constance/fields.py +69 -0
- django_cfg/apps/payments/config/constance/settings.py +160 -0
- django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
- django_cfg/apps/payments/config/helpers.py +130 -0
- django_cfg/apps/payments/management/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/__init__.py +1 -3
- django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
- django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
- django_cfg/apps/payments/middleware/__init__.py +3 -1
- django_cfg/apps/payments/middleware/api_access.py +329 -222
- django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
- django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
- django_cfg/apps/payments/migrations/0001_initial.py +708 -536
- django_cfg/apps/payments/models/__init__.py +16 -20
- django_cfg/apps/payments/models/api_keys.py +121 -43
- django_cfg/apps/payments/models/balance.py +150 -115
- django_cfg/apps/payments/models/base.py +68 -15
- django_cfg/apps/payments/models/currencies.py +207 -67
- django_cfg/apps/payments/models/managers/__init__.py +44 -0
- django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
- django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
- django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
- django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
- django_cfg/apps/payments/models/payments.py +235 -284
- django_cfg/apps/payments/models/subscriptions.py +257 -177
- django_cfg/apps/payments/models/tariffs.py +147 -40
- django_cfg/apps/payments/services/__init__.py +209 -56
- django_cfg/apps/payments/services/cache/__init__.py +6 -6
- django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
- django_cfg/apps/payments/services/core/__init__.py +10 -6
- django_cfg/apps/payments/services/core/balance_service.py +435 -360
- django_cfg/apps/payments/services/core/base.py +166 -0
- django_cfg/apps/payments/services/core/currency_service.py +478 -0
- django_cfg/apps/payments/services/core/payment_service.py +344 -468
- django_cfg/apps/payments/services/core/subscription_service.py +425 -484
- django_cfg/apps/payments/services/core/webhook_service.py +410 -0
- django_cfg/apps/payments/services/integrations/__init__.py +29 -0
- django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
- django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
- django_cfg/apps/payments/services/providers/__init__.py +9 -14
- django_cfg/apps/payments/services/providers/base.py +232 -71
- django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
- django_cfg/apps/payments/services/providers/registry.py +429 -80
- django_cfg/apps/payments/services/types/__init__.py +78 -0
- django_cfg/apps/payments/services/types/data.py +177 -0
- django_cfg/apps/payments/services/types/requests.py +150 -0
- django_cfg/apps/payments/services/types/responses.py +156 -0
- django_cfg/apps/payments/services/types/webhooks.py +232 -0
- django_cfg/apps/payments/signals/__init__.py +33 -8
- django_cfg/apps/payments/signals/api_key_signals.py +211 -130
- django_cfg/apps/payments/signals/balance_signals.py +174 -0
- django_cfg/apps/payments/signals/payment_signals.py +129 -98
- django_cfg/apps/payments/signals/subscription_signals.py +195 -143
- django_cfg/apps/payments/static/payments/css/components.css +380 -0
- django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
- django_cfg/apps/payments/static/payments/js/components.js +545 -0
- django_cfg/apps/payments/static/payments/js/utils.js +412 -0
- django_cfg/apps/payments/templatetags/__init__.py +1 -1
- django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
- django_cfg/apps/payments/urls.py +46 -47
- django_cfg/apps/payments/urls_admin.py +49 -0
- django_cfg/apps/payments/views/api/__init__.py +101 -0
- django_cfg/apps/payments/views/api/api_keys.py +387 -0
- django_cfg/apps/payments/views/api/balances.py +381 -0
- django_cfg/apps/payments/views/api/base.py +298 -0
- django_cfg/apps/payments/views/api/currencies.py +402 -0
- django_cfg/apps/payments/views/api/payments.py +415 -0
- django_cfg/apps/payments/views/api/subscriptions.py +475 -0
- django_cfg/apps/payments/views/api/webhooks.py +476 -0
- django_cfg/apps/payments/views/serializers/__init__.py +99 -0
- django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
- django_cfg/apps/payments/views/serializers/balances.py +300 -0
- django_cfg/apps/payments/views/serializers/currencies.py +335 -0
- django_cfg/apps/payments/views/serializers/payments.py +387 -0
- django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
- django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
- django_cfg/apps/tasks/urls.py +0 -2
- django_cfg/apps/tasks/urls_admin.py +14 -0
- django_cfg/apps/urls.py +4 -4
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +75 -4
- django_cfg/core/generation.py +25 -4
- django_cfg/core/integration/README.md +363 -0
- django_cfg/core/integration/__init__.py +47 -0
- django_cfg/core/integration/commands_collector.py +239 -0
- django_cfg/core/integration/display/__init__.py +15 -0
- django_cfg/core/integration/display/base.py +157 -0
- django_cfg/core/integration/display/ngrok.py +164 -0
- django_cfg/core/integration/display/startup.py +815 -0
- django_cfg/core/integration/url_integration.py +123 -0
- django_cfg/core/integration/version_checker.py +160 -0
- django_cfg/management/commands/auto_generate.py +4 -0
- django_cfg/management/commands/check_settings.py +6 -0
- django_cfg/management/commands/clear_constance.py +5 -2
- django_cfg/management/commands/create_token.py +6 -0
- django_cfg/management/commands/list_urls.py +6 -0
- django_cfg/management/commands/migrate_all.py +6 -0
- django_cfg/management/commands/migrator.py +3 -0
- django_cfg/management/commands/rundramatiq.py +6 -0
- django_cfg/management/commands/runserver_ngrok.py +51 -29
- django_cfg/management/commands/script.py +6 -0
- django_cfg/management/commands/show_config.py +12 -2
- django_cfg/management/commands/show_urls.py +4 -0
- django_cfg/management/commands/superuser.py +6 -0
- django_cfg/management/commands/task_clear.py +4 -1
- django_cfg/management/commands/task_status.py +3 -1
- django_cfg/management/commands/test_email.py +3 -0
- django_cfg/management/commands/test_telegram.py +6 -0
- django_cfg/management/commands/test_twilio.py +6 -0
- django_cfg/management/commands/tree.py +6 -0
- django_cfg/management/commands/validate_config.py +155 -149
- django_cfg/models/constance.py +31 -11
- django_cfg/models/payments.py +175 -498
- django_cfg/modules/django_currency/__init__.py +16 -11
- django_cfg/modules/django_currency/clients/__init__.py +4 -4
- django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
- django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
- django_cfg/modules/django_currency/core/__init__.py +1 -7
- django_cfg/modules/django_currency/core/converter.py +18 -23
- django_cfg/modules/django_currency/core/models.py +122 -11
- django_cfg/modules/django_currency/database/__init__.py +4 -4
- django_cfg/modules/django_currency/database/database_loader.py +190 -309
- django_cfg/modules/django_logger.py +160 -146
- django_cfg/modules/django_unfold/dashboard.py +65 -12
- django_cfg/registry/core.py +1 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/admin/components/action_grid.html +9 -9
- django_cfg/templates/admin/components/metric_card.html +5 -5
- django_cfg/templates/admin/components/status_badge.html +2 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
- django_cfg/templates/admin/snippets/components/system_health.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
- django_cfg/utils/smart_defaults.py +222 -571
- django_cfg/utils/toolkit.py +51 -11
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
- django_cfg/apps/payments/__init__.py +0 -8
- django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
- django_cfg/apps/payments/config/module.py +0 -70
- django_cfg/apps/payments/config/providers.py +0 -105
- django_cfg/apps/payments/config/settings.py +0 -96
- django_cfg/apps/payments/config/utils.py +0 -52
- django_cfg/apps/payments/decorators.py +0 -291
- django_cfg/apps/payments/management/commands/README.md +0 -178
- django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
- django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
- django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
- django_cfg/apps/payments/managers/__init__.py +0 -22
- django_cfg/apps/payments/managers/api_key_manager.py +0 -35
- django_cfg/apps/payments/managers/balance_manager.py +0 -361
- django_cfg/apps/payments/managers/currency_manager.py +0 -83
- django_cfg/apps/payments/managers/payment_manager.py +0 -44
- django_cfg/apps/payments/managers/subscription_manager.py +0 -37
- django_cfg/apps/payments/managers/tariff_manager.py +0 -29
- django_cfg/apps/payments/models/events.py +0 -73
- django_cfg/apps/payments/serializers/__init__.py +0 -56
- django_cfg/apps/payments/serializers/api_keys.py +0 -51
- django_cfg/apps/payments/serializers/balance.py +0 -59
- django_cfg/apps/payments/serializers/currencies.py +0 -55
- django_cfg/apps/payments/serializers/payments.py +0 -62
- django_cfg/apps/payments/serializers/subscriptions.py +0 -71
- django_cfg/apps/payments/serializers/tariffs.py +0 -56
- django_cfg/apps/payments/services/billing/__init__.py +0 -8
- django_cfg/apps/payments/services/cache/base.py +0 -30
- django_cfg/apps/payments/services/core/fallback_service.py +0 -432
- django_cfg/apps/payments/services/internal_types.py +0 -297
- django_cfg/apps/payments/services/middleware/__init__.py +0 -8
- django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
- django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
- django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
- django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
- django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
- django_cfg/apps/payments/services/security/__init__.py +0 -34
- django_cfg/apps/payments/services/security/error_handler.py +0 -637
- django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
- django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
- django_cfg/apps/payments/services/validators/__init__.py +0 -8
- django_cfg/apps/payments/static/payments/css/payments.css +0 -340
- django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
- django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
- django_cfg/apps/payments/static/payments/js/theme.js +0 -86
- django_cfg/apps/payments/tasks/__init__.py +0 -12
- django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
- django_cfg/apps/payments/templates/payments/base.html +0 -182
- django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
- django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
- django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
- django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
- django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
- django_cfg/apps/payments/urls_templates.py +0 -52
- django_cfg/apps/payments/utils/__init__.py +0 -45
- django_cfg/apps/payments/utils/billing_utils.py +0 -342
- django_cfg/apps/payments/utils/config_utils.py +0 -245
- django_cfg/apps/payments/utils/middleware_utils.py +0 -228
- django_cfg/apps/payments/utils/validation_utils.py +0 -94
- django_cfg/apps/payments/views/__init__.py +0 -62
- django_cfg/apps/payments/views/api_key_views.py +0 -164
- django_cfg/apps/payments/views/balance_views.py +0 -75
- django_cfg/apps/payments/views/currency_views.py +0 -111
- django_cfg/apps/payments/views/payment_views.py +0 -149
- django_cfg/apps/payments/views/subscription_views.py +0 -135
- django_cfg/apps/payments/views/tariff_views.py +0 -131
- django_cfg/apps/payments/views/templates/__init__.py +0 -25
- django_cfg/apps/payments/views/templates/ajax.py +0 -312
- django_cfg/apps/payments/views/templates/base.py +0 -204
- django_cfg/apps/payments/views/templates/dashboard.py +0 -60
- django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
- django_cfg/apps/payments/views/templates/payment_management.py +0 -164
- django_cfg/apps/payments/views/templates/qr_code.py +0 -174
- django_cfg/apps/payments/views/templates/stats.py +0 -240
- django_cfg/apps/payments/views/templates/utils.py +0 -181
- django_cfg/apps/payments/views/webhook_views.py +0 -266
- django_cfg/apps/payments/viewsets.py +0 -65
- django_cfg/core/integration.py +0 -160
- django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
- django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
- django_cfg/template_archive/.gitignore +0 -1
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/urls.py +0 -33
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,296 +1,308 @@
|
|
1
1
|
"""
|
2
|
-
Usage Tracking Middleware.
|
3
|
-
|
2
|
+
Usage Tracking Middleware for the Universal Payment System v2.0.
|
3
|
+
|
4
|
+
Lightweight middleware for tracking API usage and analytics.
|
4
5
|
"""
|
5
6
|
|
6
|
-
import
|
7
|
-
import
|
8
|
-
from typing import Optional, Dict, Any
|
7
|
+
import time
|
8
|
+
from typing import Optional
|
9
9
|
from django.http import HttpRequest, HttpResponse
|
10
10
|
from django.utils.deprecation import MiddlewareMixin
|
11
|
-
from django.conf import settings
|
12
11
|
from django.utils import timezone
|
13
|
-
from
|
14
|
-
|
12
|
+
from django.core.cache import cache
|
13
|
+
|
14
|
+
from ..config.helpers import MiddlewareConfigHelper
|
15
|
+
from django_cfg.modules.django_logger import get_logger
|
15
16
|
|
16
|
-
logger =
|
17
|
+
logger = get_logger("usage_tracking_middleware")
|
17
18
|
|
18
19
|
|
19
20
|
class UsageTrackingMiddleware(MiddlewareMixin):
|
20
21
|
"""
|
21
|
-
Middleware for
|
22
|
+
Usage Tracking Middleware for API analytics and monitoring.
|
22
23
|
|
23
24
|
Features:
|
24
|
-
- Request/response
|
25
|
-
-
|
26
|
-
-
|
27
|
-
- Performance
|
28
|
-
- Error tracking
|
25
|
+
- Request/response time tracking
|
26
|
+
- Endpoint usage statistics
|
27
|
+
- User activity monitoring
|
28
|
+
- Performance metrics
|
29
|
+
- Error rate tracking
|
29
30
|
"""
|
30
31
|
|
31
32
|
def __init__(self, get_response=None):
|
32
33
|
super().__init__(get_response)
|
33
|
-
self.redis_service = RedisService()
|
34
|
-
|
35
|
-
# Enable/disable usage tracking
|
36
|
-
self.enabled = getattr(settings, 'PAYMENTS_USAGE_TRACKING_ENABLED', True)
|
37
|
-
|
38
|
-
# Paths to track (empty means track all API paths)
|
39
|
-
self.tracked_paths = getattr(settings, 'PAYMENTS_TRACKED_PATHS', [])
|
40
|
-
|
41
|
-
# Paths to exclude from tracking
|
42
|
-
self.excluded_paths = getattr(settings, 'PAYMENTS_EXCLUDED_PATHS', [
|
43
|
-
'/admin/',
|
44
|
-
'/cfg/',
|
45
|
-
'/api/v1/api-key/validate/',
|
46
|
-
])
|
47
34
|
|
48
|
-
#
|
49
|
-
|
35
|
+
# Load configuration from django-cfg
|
36
|
+
try:
|
37
|
+
middleware_config = MiddlewareConfigHelper.get_middleware_config()
|
38
|
+
|
39
|
+
# Configuration from django-cfg
|
40
|
+
self.enabled = middleware_config['usage_tracking_enabled']
|
41
|
+
self.track_anonymous = middleware_config['track_anonymous_usage']
|
42
|
+
self.api_prefixes = middleware_config['api_prefixes']
|
43
|
+
self.cache_timeout = middleware_config['cache_timeouts']['default']
|
44
|
+
|
45
|
+
# Static exempt paths
|
46
|
+
self.exempt_paths = [
|
47
|
+
'/api/health/',
|
48
|
+
'/admin/',
|
49
|
+
'/static/',
|
50
|
+
'/media/',
|
51
|
+
]
|
52
|
+
|
53
|
+
except Exception as e:
|
54
|
+
logger.warning(f"Failed to load usage tracking config, using defaults: {e}")
|
55
|
+
# Fallback defaults
|
56
|
+
self.enabled = True
|
57
|
+
self.track_anonymous = False
|
58
|
+
self.api_prefixes = ['/api/']
|
59
|
+
self.exempt_paths = ['/api/health/', '/admin/']
|
60
|
+
self.cache_timeout = 3600
|
50
61
|
|
51
|
-
|
52
|
-
|
62
|
+
logger.info(f"Usage Tracking Middleware initialized", extra={
|
63
|
+
'enabled': self.enabled,
|
64
|
+
'track_anonymous': self.track_anonymous
|
65
|
+
})
|
53
66
|
|
54
|
-
def process_request(self, request: HttpRequest) ->
|
55
|
-
"""
|
56
|
-
|
67
|
+
def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
|
68
|
+
"""
|
69
|
+
Process incoming request - start timing and prepare tracking.
|
70
|
+
"""
|
57
71
|
if not self.enabled:
|
58
|
-
return
|
59
|
-
|
60
|
-
# Skip excluded paths
|
61
|
-
if self._is_excluded_path(request):
|
62
|
-
return
|
72
|
+
return None
|
63
73
|
|
64
|
-
#
|
65
|
-
if not
|
66
|
-
return
|
74
|
+
# Check if we should track this request
|
75
|
+
if not self._should_track_request(request):
|
76
|
+
return None
|
67
77
|
|
68
|
-
#
|
69
|
-
request._usage_start_time =
|
78
|
+
# Start timing
|
79
|
+
request._usage_start_time = time.time()
|
70
80
|
|
71
|
-
#
|
72
|
-
request.
|
73
|
-
'api_key_id': request.payment_api_key.id,
|
74
|
-
'user_id': request.payment_api_key.user.id,
|
81
|
+
# Store request info for tracking
|
82
|
+
request._usage_info = {
|
75
83
|
'method': request.method,
|
76
84
|
'path': request.path,
|
77
|
-
'
|
78
|
-
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
|
85
|
+
'user_agent': request.META.get('HTTP_USER_AGENT', 'unknown'),
|
79
86
|
'ip_address': self._get_client_ip(request),
|
80
|
-
'
|
87
|
+
'authenticated': hasattr(request, 'api_key') or (hasattr(request, 'user') and request.user.is_authenticated)
|
81
88
|
}
|
82
89
|
|
83
|
-
|
84
|
-
if hasattr(request, 'payment_subscription'):
|
85
|
-
request._usage_data.update({
|
86
|
-
'subscription_id': request.payment_subscription.id,
|
87
|
-
'endpoint_group_id': request.payment_subscription.endpoint_group.id,
|
88
|
-
'tier': request.payment_subscription.tier,
|
89
|
-
})
|
90
|
-
|
91
|
-
# Track request body if enabled and safe
|
92
|
-
if self.track_request_body and self._is_safe_to_track_body(request):
|
93
|
-
try:
|
94
|
-
request._usage_data['request_body'] = request.body.decode('utf-8')[:1000] # Limit size
|
95
|
-
except:
|
96
|
-
pass
|
90
|
+
return None
|
97
91
|
|
98
92
|
def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
|
99
|
-
"""
|
100
|
-
|
101
|
-
|
93
|
+
"""
|
94
|
+
Process response - track usage and performance metrics.
|
95
|
+
"""
|
96
|
+
if not self.enabled or not hasattr(request, '_usage_start_time'):
|
102
97
|
return response
|
103
98
|
|
104
99
|
try:
|
105
100
|
# Calculate response time
|
106
|
-
|
107
|
-
response_time_ms = int((end_time - request._usage_start_time).total_seconds() * 1000)
|
108
|
-
|
109
|
-
# Update usage data
|
110
|
-
usage_data = request._usage_data
|
111
|
-
usage_data.update({
|
112
|
-
'end_time': end_time,
|
113
|
-
'response_time_ms': response_time_ms,
|
114
|
-
'status_code': response.status_code,
|
115
|
-
'response_size': len(response.content) if hasattr(response, 'content') else 0,
|
116
|
-
})
|
101
|
+
response_time = (time.time() - request._usage_start_time) * 1000 # ms
|
117
102
|
|
118
|
-
#
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
pass
|
127
|
-
|
128
|
-
# Track error details for failed requests
|
129
|
-
if response.status_code >= 400:
|
130
|
-
usage_data['is_error'] = True
|
131
|
-
usage_data['error_category'] = self._categorize_error(response.status_code)
|
132
|
-
else:
|
133
|
-
usage_data['is_error'] = False
|
134
|
-
|
135
|
-
# Store in Redis for real-time analytics
|
136
|
-
self._store_usage_data(usage_data)
|
137
|
-
|
138
|
-
# Create billing transaction if needed
|
139
|
-
if hasattr(request, 'payment_subscription'):
|
140
|
-
self._create_billing_transaction(request.payment_subscription, usage_data)
|
141
|
-
|
142
|
-
# Log for debugging
|
143
|
-
logger.info(
|
144
|
-
f"API usage tracked - User: {usage_data['user_id']}, "
|
145
|
-
f"Path: {usage_data['path']}, "
|
146
|
-
f"Status: {usage_data['status_code']}, "
|
147
|
-
f"Time: {response_time_ms}ms"
|
148
|
-
)
|
103
|
+
# Get usage info
|
104
|
+
usage_info = getattr(request, '_usage_info', {})
|
105
|
+
|
106
|
+
# Track the request
|
107
|
+
self._track_request(request, response, response_time, usage_info)
|
108
|
+
|
109
|
+
# Add performance headers
|
110
|
+
response['X-Response-Time'] = f"{response_time:.2f}ms"
|
149
111
|
|
150
112
|
except Exception as e:
|
151
|
-
logger.
|
113
|
+
logger.warning(f"Usage tracking failed: {e}")
|
152
114
|
|
153
115
|
return response
|
154
116
|
|
155
|
-
def
|
156
|
-
"""
|
157
|
-
|
158
|
-
|
159
|
-
# Check
|
160
|
-
for
|
161
|
-
if path.startswith(excluded):
|
162
|
-
return True
|
163
|
-
|
164
|
-
# If tracked_paths is specified, only track those
|
165
|
-
if self.tracked_paths:
|
166
|
-
return not any(path.startswith(tracked) for tracked in self.tracked_paths)
|
167
|
-
|
168
|
-
return False
|
169
|
-
|
170
|
-
def _get_client_ip(self, request: HttpRequest) -> Optional[str]:
|
171
|
-
"""Get client IP address."""
|
117
|
+
def _should_track_request(self, request: HttpRequest) -> bool:
|
118
|
+
"""
|
119
|
+
Determine if we should track this request.
|
120
|
+
"""
|
121
|
+
# Check if path is in API prefixes
|
122
|
+
is_api_request = any(request.path.startswith(prefix) for prefix in self.api_prefixes)
|
172
123
|
|
173
|
-
|
174
|
-
forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
175
|
-
if forwarded_for:
|
176
|
-
return forwarded_for.split(',')[0].strip()
|
177
|
-
|
178
|
-
# Check for real IP header
|
179
|
-
real_ip = request.META.get('HTTP_X_REAL_IP')
|
180
|
-
if real_ip:
|
181
|
-
return real_ip
|
182
|
-
|
183
|
-
# Fallback to remote address
|
184
|
-
return request.META.get('REMOTE_ADDR')
|
185
|
-
|
186
|
-
def _is_safe_to_track_body(self, request: HttpRequest) -> bool:
|
187
|
-
"""Check if it's safe to track request body."""
|
188
|
-
|
189
|
-
# Don't track large bodies
|
190
|
-
content_length = request.META.get('CONTENT_LENGTH')
|
191
|
-
if content_length and int(content_length) > 10000: # 10KB limit
|
124
|
+
if not is_api_request:
|
192
125
|
return False
|
193
126
|
|
194
|
-
#
|
195
|
-
|
196
|
-
if 'multipart/form-data' in content_type:
|
127
|
+
# Check exempt paths
|
128
|
+
if request.path in self.exempt_paths:
|
197
129
|
return False
|
198
130
|
|
199
|
-
#
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
131
|
+
# Check if we should track anonymous requests
|
132
|
+
if not self.track_anonymous:
|
133
|
+
is_authenticated = (
|
134
|
+
hasattr(request, 'api_key') or
|
135
|
+
(hasattr(request, 'user') and request.user.is_authenticated)
|
136
|
+
)
|
137
|
+
if not is_authenticated:
|
138
|
+
return False
|
204
139
|
|
205
140
|
return True
|
206
141
|
|
207
|
-
def
|
208
|
-
"""
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
142
|
+
def _track_request(self, request: HttpRequest, response: HttpResponse, response_time: float, usage_info: dict):
|
143
|
+
"""
|
144
|
+
Track request usage and performance metrics.
|
145
|
+
"""
|
146
|
+
try:
|
147
|
+
# Get user identifier
|
148
|
+
user_id = None
|
149
|
+
if hasattr(request, 'api_key'):
|
150
|
+
user_id = request.api_key.user.id
|
151
|
+
elif hasattr(request, 'user') and request.user.is_authenticated:
|
152
|
+
user_id = request.user.id
|
153
|
+
|
154
|
+
# Track endpoint usage
|
155
|
+
self._track_endpoint_usage(usage_info['path'], usage_info['method'], response.status_code)
|
156
|
+
|
157
|
+
# Track user activity
|
158
|
+
if user_id:
|
159
|
+
self._track_user_activity(user_id, usage_info['path'], response_time)
|
160
|
+
|
161
|
+
# Track performance metrics
|
162
|
+
self._track_performance_metrics(usage_info['path'], response_time, response.status_code)
|
163
|
+
|
164
|
+
# Track errors
|
165
|
+
if response.status_code >= 400:
|
166
|
+
self._track_error(usage_info, response.status_code)
|
167
|
+
|
168
|
+
# Log request
|
169
|
+
logger.debug(f"Request tracked", extra={
|
170
|
+
'path': usage_info['path'],
|
171
|
+
'method': usage_info['method'],
|
172
|
+
'status_code': response.status_code,
|
173
|
+
'response_time_ms': round(response_time, 2),
|
174
|
+
'user_id': user_id,
|
175
|
+
'authenticated': usage_info['authenticated']
|
176
|
+
})
|
177
|
+
|
178
|
+
except Exception as e:
|
179
|
+
logger.warning(f"Failed to track request: {e}")
|
220
180
|
|
221
|
-
def
|
222
|
-
"""
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
181
|
+
def _track_endpoint_usage(self, path: str, method: str, status_code: int):
|
182
|
+
"""
|
183
|
+
Track endpoint usage statistics.
|
184
|
+
"""
|
185
|
+
try:
|
186
|
+
today = timezone.now().date().isoformat()
|
187
|
+
hour = timezone.now().hour
|
188
|
+
|
189
|
+
# Daily endpoint usage
|
190
|
+
daily_key = f"endpoint_usage:{path}:{method}:{today}"
|
191
|
+
cache.set(daily_key, cache.get(daily_key, 0) + 1, timeout=86400 * 2)
|
192
|
+
|
193
|
+
# Hourly endpoint usage
|
194
|
+
hourly_key = f"endpoint_usage_hourly:{path}:{method}:{today}:{hour}"
|
195
|
+
cache.set(hourly_key, cache.get(hourly_key, 0) + 1, timeout=86400)
|
196
|
+
|
197
|
+
# Status code tracking
|
198
|
+
status_key = f"endpoint_status:{path}:{method}:{status_code}:{today}"
|
199
|
+
cache.set(status_key, cache.get(status_key, 0) + 1, timeout=86400 * 2)
|
200
|
+
|
201
|
+
except Exception as e:
|
202
|
+
logger.warning(f"Failed to track endpoint usage: {e}")
|
230
203
|
|
231
|
-
def
|
232
|
-
"""
|
233
|
-
|
204
|
+
def _track_user_activity(self, user_id: int, path: str, response_time: float):
|
205
|
+
"""
|
206
|
+
Track user activity and performance.
|
207
|
+
"""
|
208
|
+
try:
|
209
|
+
today = timezone.now().date().isoformat()
|
210
|
+
|
211
|
+
# Update last activity
|
212
|
+
cache.set(f"user_last_activity:{user_id}", timezone.now().isoformat(), timeout=86400 * 30)
|
213
|
+
|
214
|
+
# Daily user requests
|
215
|
+
daily_requests_key = f"user_requests:{user_id}:{today}"
|
216
|
+
cache.set(daily_requests_key, cache.get(daily_requests_key, 0) + 1, timeout=86400 * 2)
|
217
|
+
|
218
|
+
# User endpoint usage
|
219
|
+
user_endpoint_key = f"user_endpoint:{user_id}:{path}:{today}"
|
220
|
+
cache.set(user_endpoint_key, cache.get(user_endpoint_key, 0) + 1, timeout=86400 * 2)
|
221
|
+
|
222
|
+
# User performance tracking
|
223
|
+
perf_key = f"user_performance:{user_id}:{today}"
|
224
|
+
perf_data = cache.get(perf_key, {'total_time': 0.0, 'request_count': 0})
|
225
|
+
perf_data['total_time'] += response_time
|
226
|
+
perf_data['request_count'] += 1
|
227
|
+
perf_data['avg_response_time'] = perf_data['total_time'] / perf_data['request_count']
|
228
|
+
|
229
|
+
cache.set(perf_key, perf_data, timeout=86400 * 2)
|
230
|
+
|
231
|
+
except Exception as e:
|
232
|
+
logger.warning(f"Failed to track user activity: {e}")
|
233
|
+
|
234
|
+
def _track_performance_metrics(self, path: str, response_time: float, status_code: int):
|
235
|
+
"""
|
236
|
+
Track performance metrics for monitoring.
|
237
|
+
"""
|
234
238
|
try:
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
if
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
)
|
239
|
+
today = timezone.now().date().isoformat()
|
240
|
+
|
241
|
+
# Global performance metrics
|
242
|
+
global_perf_key = f"global_performance:{today}"
|
243
|
+
global_perf = cache.get(global_perf_key, {
|
244
|
+
'total_requests': 0,
|
245
|
+
'total_time': 0.0,
|
246
|
+
'slow_requests': 0, # > 1000ms
|
247
|
+
'fast_requests': 0, # < 100ms
|
248
|
+
})
|
249
|
+
|
250
|
+
global_perf['total_requests'] += 1
|
251
|
+
global_perf['total_time'] += response_time
|
252
|
+
|
253
|
+
if response_time > 1000:
|
254
|
+
global_perf['slow_requests'] += 1
|
255
|
+
elif response_time < 100:
|
256
|
+
global_perf['fast_requests'] += 1
|
257
|
+
|
258
|
+
global_perf['avg_response_time'] = global_perf['total_time'] / global_perf['total_requests']
|
259
|
+
|
260
|
+
cache.set(global_perf_key, global_perf, timeout=86400 * 2)
|
261
|
+
|
262
|
+
# Update system average response time for adaptive rate limiting
|
263
|
+
cache.set('system_avg_response_time', global_perf['avg_response_time'], timeout=300)
|
261
264
|
|
262
265
|
except Exception as e:
|
263
|
-
logger.
|
266
|
+
logger.warning(f"Failed to track performance metrics: {e}")
|
264
267
|
|
265
|
-
def
|
266
|
-
"""
|
267
|
-
|
268
|
+
def _track_error(self, usage_info: dict, status_code: int):
|
269
|
+
"""
|
270
|
+
Track error occurrences for monitoring.
|
271
|
+
"""
|
268
272
|
try:
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
#
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
#
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
'endpoint': usage_data['path'],
|
289
|
-
'method': usage_data['method'],
|
290
|
-
'response_time_ms': usage_data['response_time_ms'],
|
291
|
-
'status_code': usage_data['status_code'],
|
292
|
-
}
|
293
|
-
)
|
273
|
+
today = timezone.now().date().isoformat()
|
274
|
+
|
275
|
+
# Global error tracking
|
276
|
+
error_key = f"errors:{status_code}:{today}"
|
277
|
+
cache.set(error_key, cache.get(error_key, 0) + 1, timeout=86400 * 7)
|
278
|
+
|
279
|
+
# Endpoint-specific error tracking
|
280
|
+
endpoint_error_key = f"endpoint_errors:{usage_info['path']}:{status_code}:{today}"
|
281
|
+
cache.set(endpoint_error_key, cache.get(endpoint_error_key, 0) + 1, timeout=86400 * 7)
|
282
|
+
|
283
|
+
# Log significant errors
|
284
|
+
if status_code >= 500:
|
285
|
+
logger.error(f"Server error tracked", extra={
|
286
|
+
'status_code': status_code,
|
287
|
+
'path': usage_info['path'],
|
288
|
+
'method': usage_info['method'],
|
289
|
+
'ip_address': usage_info['ip_address'],
|
290
|
+
'user_agent': usage_info['user_agent']
|
291
|
+
})
|
294
292
|
|
295
293
|
except Exception as e:
|
296
|
-
logger.
|
294
|
+
logger.warning(f"Failed to track error: {e}")
|
295
|
+
|
296
|
+
def _get_client_ip(self, request: HttpRequest) -> str:
|
297
|
+
"""
|
298
|
+
Get client IP address from request.
|
299
|
+
"""
|
300
|
+
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
301
|
+
if x_forwarded_for:
|
302
|
+
return x_forwarded_for.split(',')[0].strip()
|
303
|
+
|
304
|
+
x_real_ip = request.META.get('HTTP_X_REAL_IP')
|
305
|
+
if x_real_ip:
|
306
|
+
return x_real_ip
|
307
|
+
|
308
|
+
return request.META.get('REMOTE_ADDR', 'unknown')
|