django-cfg 1.3.7__py3-none-any.whl β 1.3.11__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/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html β overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info β django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info β django_cfg-1.3.11.dist-info}/RECORD +198 -160
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold β django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold β django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold β django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold β django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info β django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info β django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info β django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,308 @@
|
|
1
|
+
"""
|
2
|
+
Background tasks for API usage tracking and statistics.
|
3
|
+
"""
|
4
|
+
import dramatiq
|
5
|
+
import logging
|
6
|
+
import time
|
7
|
+
from typing import Dict, Any, List
|
8
|
+
from django.db import transaction
|
9
|
+
from django.db.models import F
|
10
|
+
from django.utils import timezone
|
11
|
+
from django.core.cache import cache
|
12
|
+
|
13
|
+
from ..models import APIKey, Subscription
|
14
|
+
from .types import (
|
15
|
+
TaskResult,
|
16
|
+
UsageUpdateRequest,
|
17
|
+
UsageUpdateResult,
|
18
|
+
BatchUpdateResult,
|
19
|
+
CleanupResult
|
20
|
+
)
|
21
|
+
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
@dramatiq.actor(queue_name="payments")
|
25
|
+
def update_api_key_usage_async(
|
26
|
+
api_key_id: str,
|
27
|
+
ip_address: str = None,
|
28
|
+
increment: int = 1
|
29
|
+
) -> UsageUpdateResult:
|
30
|
+
"""
|
31
|
+
Update API key usage counters asynchronously.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
api_key_id: API key UUID to update
|
35
|
+
ip_address: Client IP address for logging
|
36
|
+
increment: Number to increment (default: 1)
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
Update result with statistics
|
40
|
+
"""
|
41
|
+
start_time = time.time()
|
42
|
+
|
43
|
+
try:
|
44
|
+
with transaction.atomic():
|
45
|
+
# Use F() expressions for atomic updates
|
46
|
+
updated_count = APIKey.objects.filter(id=api_key_id).update(
|
47
|
+
total_requests=F('total_requests') + increment,
|
48
|
+
last_used_at=timezone.now(),
|
49
|
+
updated_at=timezone.now()
|
50
|
+
)
|
51
|
+
|
52
|
+
if updated_count == 0:
|
53
|
+
logger.warning(f"API key not found: {api_key_id}")
|
54
|
+
return UsageUpdateResult(
|
55
|
+
status='error',
|
56
|
+
error='API key not found',
|
57
|
+
resource_id=api_key_id,
|
58
|
+
increment=increment
|
59
|
+
)
|
60
|
+
|
61
|
+
# Get updated values for logging
|
62
|
+
api_key = APIKey.objects.get(id=api_key_id)
|
63
|
+
|
64
|
+
processing_time = (time.time() - start_time) * 1000
|
65
|
+
|
66
|
+
logger.debug(f"API key usage updated", extra={
|
67
|
+
'api_key_id': api_key_id,
|
68
|
+
'user_id': api_key.user.id,
|
69
|
+
'total_requests': api_key.total_requests,
|
70
|
+
'increment': increment,
|
71
|
+
'ip_address': ip_address,
|
72
|
+
'processing_time_ms': round(processing_time, 2)
|
73
|
+
})
|
74
|
+
|
75
|
+
return UsageUpdateResult(
|
76
|
+
status='success',
|
77
|
+
resource_id=api_key_id,
|
78
|
+
total_requests=api_key.total_requests,
|
79
|
+
increment=increment,
|
80
|
+
user_id=api_key.user.id,
|
81
|
+
processing_time_ms=round(processing_time, 2)
|
82
|
+
)
|
83
|
+
|
84
|
+
except Exception as e:
|
85
|
+
logger.error(f"Failed to update API key usage", extra={
|
86
|
+
'api_key_id': api_key_id,
|
87
|
+
'error': str(e),
|
88
|
+
'ip_address': ip_address
|
89
|
+
})
|
90
|
+
raise # Re-raise for Dramatiq retry logic
|
91
|
+
|
92
|
+
@dramatiq.actor(queue_name="payments")
|
93
|
+
def update_subscription_usage_async(
|
94
|
+
subscription_id: str,
|
95
|
+
increment: int = 1
|
96
|
+
) -> UsageUpdateResult:
|
97
|
+
"""
|
98
|
+
Update subscription usage counters asynchronously.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
subscription_id: Subscription UUID to update
|
102
|
+
increment: Number to increment (default: 1)
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
Update result with statistics
|
106
|
+
"""
|
107
|
+
start_time = time.time()
|
108
|
+
|
109
|
+
try:
|
110
|
+
with transaction.atomic():
|
111
|
+
# Use F() expressions for atomic updates
|
112
|
+
updated_count = Subscription.objects.filter(id=subscription_id).update(
|
113
|
+
total_requests=F('total_requests') + increment,
|
114
|
+
last_request_at=timezone.now(),
|
115
|
+
updated_at=timezone.now()
|
116
|
+
)
|
117
|
+
|
118
|
+
if updated_count == 0:
|
119
|
+
logger.warning(f"Subscription not found: {subscription_id}")
|
120
|
+
return UsageUpdateResult(
|
121
|
+
status='error',
|
122
|
+
error='Subscription not found',
|
123
|
+
resource_id=subscription_id,
|
124
|
+
increment=increment
|
125
|
+
)
|
126
|
+
|
127
|
+
# Get updated values for logging
|
128
|
+
subscription = Subscription.objects.get(id=subscription_id)
|
129
|
+
|
130
|
+
processing_time = (time.time() - start_time) * 1000
|
131
|
+
|
132
|
+
logger.debug(f"Subscription usage updated", extra={
|
133
|
+
'subscription_id': subscription_id,
|
134
|
+
'user_id': subscription.user.id,
|
135
|
+
'total_requests': subscription.total_requests,
|
136
|
+
'increment': increment,
|
137
|
+
'processing_time_ms': round(processing_time, 2)
|
138
|
+
})
|
139
|
+
|
140
|
+
return UsageUpdateResult(
|
141
|
+
status='success',
|
142
|
+
resource_id=subscription_id,
|
143
|
+
total_requests=subscription.total_requests,
|
144
|
+
increment=increment,
|
145
|
+
user_id=subscription.user.id,
|
146
|
+
processing_time_ms=round(processing_time, 2)
|
147
|
+
)
|
148
|
+
|
149
|
+
except Exception as e:
|
150
|
+
logger.error(f"Failed to update subscription usage", extra={
|
151
|
+
'subscription_id': subscription_id,
|
152
|
+
'error': str(e)
|
153
|
+
})
|
154
|
+
raise # Re-raise for Dramatiq retry logic
|
155
|
+
|
156
|
+
@dramatiq.actor(queue_name="payments")
|
157
|
+
def batch_update_usage_counters() -> BatchUpdateResult:
|
158
|
+
"""
|
159
|
+
Batch update usage counters from cache to reduce database load.
|
160
|
+
|
161
|
+
This task processes accumulated usage data from Redis cache
|
162
|
+
and performs batch updates to the database.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
Batch processing results
|
166
|
+
"""
|
167
|
+
start_time = time.time()
|
168
|
+
api_keys_updated = 0
|
169
|
+
subscriptions_updated = 0
|
170
|
+
errors = []
|
171
|
+
|
172
|
+
try:
|
173
|
+
# Process API key usage counters
|
174
|
+
api_key_pattern = "api_usage_pending:*"
|
175
|
+
api_key_keys = cache.keys(api_key_pattern)
|
176
|
+
|
177
|
+
for cache_key in api_key_keys:
|
178
|
+
try:
|
179
|
+
# Extract API key ID from cache key
|
180
|
+
api_key_id = cache_key.split(':')[-1]
|
181
|
+
pending_count = cache.get(cache_key, 0)
|
182
|
+
|
183
|
+
if pending_count > 0:
|
184
|
+
# Update in background
|
185
|
+
update_api_key_usage_async.send(
|
186
|
+
api_key_id=api_key_id,
|
187
|
+
increment=pending_count
|
188
|
+
)
|
189
|
+
|
190
|
+
# Clear cache
|
191
|
+
cache.delete(cache_key)
|
192
|
+
api_keys_updated += 1
|
193
|
+
|
194
|
+
except Exception as e:
|
195
|
+
errors.append({
|
196
|
+
'type': 'api_key',
|
197
|
+
'cache_key': cache_key,
|
198
|
+
'error': str(e)
|
199
|
+
})
|
200
|
+
|
201
|
+
# Process subscription usage counters
|
202
|
+
subscription_pattern = "subscription_usage_pending:*"
|
203
|
+
subscription_keys = cache.keys(subscription_pattern)
|
204
|
+
|
205
|
+
for cache_key in subscription_keys:
|
206
|
+
try:
|
207
|
+
# Extract subscription ID from cache key
|
208
|
+
subscription_id = cache_key.split(':')[-1]
|
209
|
+
pending_count = cache.get(cache_key, 0)
|
210
|
+
|
211
|
+
if pending_count > 0:
|
212
|
+
# Update in background
|
213
|
+
update_subscription_usage_async.send(
|
214
|
+
subscription_id=subscription_id,
|
215
|
+
increment=pending_count
|
216
|
+
)
|
217
|
+
|
218
|
+
# Clear cache
|
219
|
+
cache.delete(cache_key)
|
220
|
+
subscriptions_updated += 1
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
errors.append({
|
224
|
+
'type': 'subscription',
|
225
|
+
'cache_key': cache_key,
|
226
|
+
'error': str(e)
|
227
|
+
})
|
228
|
+
|
229
|
+
processing_time = (time.time() - start_time) * 1000
|
230
|
+
|
231
|
+
logger.info(f"Batch usage update completed", extra={
|
232
|
+
'api_keys_updated': api_keys_updated,
|
233
|
+
'subscriptions_updated': subscriptions_updated,
|
234
|
+
'errors_count': len(errors),
|
235
|
+
'processing_time_ms': round(processing_time, 2)
|
236
|
+
})
|
237
|
+
|
238
|
+
return BatchUpdateResult(
|
239
|
+
status='success',
|
240
|
+
api_keys_updated=api_keys_updated,
|
241
|
+
subscriptions_updated=subscriptions_updated,
|
242
|
+
errors=errors,
|
243
|
+
total_items=api_keys_updated + subscriptions_updated,
|
244
|
+
processing_time_ms=round(processing_time, 2)
|
245
|
+
)
|
246
|
+
|
247
|
+
except Exception as e:
|
248
|
+
logger.error(f"Batch usage update failed: {e}")
|
249
|
+
errors.append({
|
250
|
+
'type': 'batch_processing',
|
251
|
+
'error': str(e)
|
252
|
+
})
|
253
|
+
return BatchUpdateResult(
|
254
|
+
status='error',
|
255
|
+
api_keys_updated=api_keys_updated,
|
256
|
+
subscriptions_updated=subscriptions_updated,
|
257
|
+
errors=errors,
|
258
|
+
total_items=api_keys_updated + subscriptions_updated,
|
259
|
+
error=str(e)
|
260
|
+
)
|
261
|
+
|
262
|
+
@dramatiq.actor(queue_name="payments")
|
263
|
+
def cleanup_stale_usage_cache() -> CleanupResult:
|
264
|
+
"""
|
265
|
+
Cleanup stale usage tracking cache entries.
|
266
|
+
|
267
|
+
Removes old cache entries that might have been left behind
|
268
|
+
due to processing errors or system restarts.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
Cleanup results
|
272
|
+
"""
|
273
|
+
try:
|
274
|
+
cleanup_count = 0
|
275
|
+
|
276
|
+
# Cleanup old API key usage cache
|
277
|
+
api_key_keys = cache.keys("api_usage_pending:*")
|
278
|
+
for key in api_key_keys:
|
279
|
+
# Check if cache entry is older than 1 hour
|
280
|
+
ttl = cache.ttl(key)
|
281
|
+
if ttl is not None and ttl < 3600: # Less than 1 hour remaining
|
282
|
+
cache.delete(key)
|
283
|
+
cleanup_count += 1
|
284
|
+
|
285
|
+
# Cleanup old subscription usage cache
|
286
|
+
subscription_keys = cache.keys("subscription_usage_pending:*")
|
287
|
+
for key in subscription_keys:
|
288
|
+
ttl = cache.ttl(key)
|
289
|
+
if ttl is not None and ttl < 3600:
|
290
|
+
cache.delete(key)
|
291
|
+
cleanup_count += 1
|
292
|
+
|
293
|
+
logger.info(f"Cleaned up {cleanup_count} stale cache entries")
|
294
|
+
|
295
|
+
return CleanupResult(
|
296
|
+
status='completed',
|
297
|
+
cleaned_entries=cleanup_count,
|
298
|
+
cleanup_type='stale_usage_cache'
|
299
|
+
)
|
300
|
+
|
301
|
+
except Exception as e:
|
302
|
+
logger.error(f"Cache cleanup failed: {e}")
|
303
|
+
return CleanupResult(
|
304
|
+
status='error',
|
305
|
+
error=str(e),
|
306
|
+
cleaned_entries=0,
|
307
|
+
cleanup_type='stale_usage_cache'
|
308
|
+
)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Reusable dashboard header component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="Payment Overview" icon="π³" %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- title: Dashboard title
|
9
|
+
- icon: Emoji or icon to display
|
10
|
+
- subtitle: Optional subtitle
|
11
|
+
{% endcomment %}
|
12
|
+
|
13
|
+
<div class="bg-white border border-base-200 dark:bg-base-900 dark:border-base-700 p-4 rounded-default mb-4">
|
14
|
+
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark mb-3">
|
15
|
+
{% if icon %}{{ icon }} {% endif %}{{ title }}
|
16
|
+
{% if subtitle %}
|
17
|
+
<span class="text-sm font-normal text-font-subtle-light dark:text-font-subtle-dark">{{ subtitle }}</span>
|
18
|
+
{% endif %}
|
19
|
+
</h3>
|
20
|
+
|
21
|
+
{% block dashboard_content %}
|
22
|
+
{% endblock %}
|
23
|
+
</div>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Reusable stats card component for payment admin dashboards
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Users" value="1,234" color="primary" icon="π₯" %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- title: Card title
|
9
|
+
- value: Main value to display
|
10
|
+
- color: Color theme (primary, success, warning, error, info)
|
11
|
+
- icon: Emoji or icon to display
|
12
|
+
- subtitle: Optional subtitle text
|
13
|
+
{% endcomment %}
|
14
|
+
|
15
|
+
{% load humanize %}
|
16
|
+
|
17
|
+
<div class="text-center bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
18
|
+
<p class="text-2xl font-bold text-{{ color|default:'primary' }}-600 dark:text-{{ color|default:'primary' }}-400">
|
19
|
+
{% if icon %}<span class="mr-1">{{ icon }}</span>{% endif %}{{ value|default:0 }}
|
20
|
+
</p>
|
21
|
+
<p class="text-sm text-font-subtle-light dark:text-font-subtle-dark">{{ title }}</p>
|
22
|
+
{% if subtitle %}
|
23
|
+
<p class="text-xs text-gray-500">{{ subtitle }}</p>
|
24
|
+
{% endif %}
|
25
|
+
</div>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Reusable stats grid component for payment admin dashboards
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include "admin/payments/_components/stats_grid.html" with stats=stats_data columns=4 %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- stats: List of stat objects with title, value, color, icon properties
|
9
|
+
- columns: Number of columns (2, 3, 4, 5)
|
10
|
+
{% endcomment %}
|
11
|
+
|
12
|
+
<div class="grid grid-cols-{{ columns|default:4 }} gap-4 mb-4">
|
13
|
+
{% for stat in stats %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title=stat.title value=stat.value color=stat.color icon=stat.icon subtitle=stat.subtitle %}
|
15
|
+
{% endfor %}
|
16
|
+
</div>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
2
|
+
{% load static %}
|
3
|
+
|
4
|
+
{% block result_list %}
|
5
|
+
<!-- API Key Statistics Dashboard -->
|
6
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="API Keys Overview" icon="π" %}
|
7
|
+
|
8
|
+
{% if api_key_stats %}
|
9
|
+
{% with stats=api_key_stats %}
|
10
|
+
<div class="grid grid-cols-4 gap-4 mb-4">
|
11
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Keys" value=stats.total_keys color="primary" %}
|
12
|
+
{% include "admin/payments/_components/stats_card.html" with title="Active" value=stats.active_keys color="success" %}
|
13
|
+
{% include "admin/payments/_components/stats_card.html" with title="Expiring Soon" value=stats.expiring_soon color="warning" %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title="Expired" value=stats.expired_keys color="error" %}
|
15
|
+
</div>
|
16
|
+
{% endwith %}
|
17
|
+
{% endif %}
|
18
|
+
|
19
|
+
<div class="grid grid-cols-2 gap-4">
|
20
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
21
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Usage Statistics</h4>
|
22
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
23
|
+
<li>π Total Requests: <span class="font-medium text-primary-600 dark:text-primary-400">{{ stats.total_requests|default:0 }}</span></li>
|
24
|
+
<li>π Recently Used: <span class="font-medium text-success-600 dark:text-success-400">{{ stats.recently_used|default:0 }}</span></li>
|
25
|
+
<li>π Unused: <span class="font-medium text-warning-600 dark:text-warning-400">{{ stats.unused_keys|default:0 }}</span></li>
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
29
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π Security</h4>
|
30
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
31
|
+
<li>π₯ Heavy Usage: <span class="font-medium text-purple-600 dark:text-purple-400">{{ stats.heavy_usage_keys|default:0 }}</span></li>
|
32
|
+
<li>β οΈ Old Unused: <span class="font-medium text-error-600 dark:text-error-400">{{ stats.never_used_old_keys|default:0 }}</span></li>
|
33
|
+
<li>π₯ Top Users: <span class="font-medium text-info-600 dark:text-info-400">{{ stats.top_users|length|default:0 }}</span></li>
|
34
|
+
</ul>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
{{ block.super }}
|
39
|
+
{% endblock %}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
2
|
+
{% load static %}
|
3
|
+
|
4
|
+
{% block result_list %}
|
5
|
+
<!-- Balance Statistics Dashboard -->
|
6
|
+
{% if balance_stats %}
|
7
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="Balance Overview" icon="π°" %}
|
8
|
+
|
9
|
+
{% with stats=balance_stats %}
|
10
|
+
<div class="grid grid-cols-4 gap-4 mb-4">
|
11
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Balances" value=stats.total_balances color="primary" %}
|
12
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Value" value="$"|add:stats.total_balance|floatformat:2 color="success" %}
|
13
|
+
{% include "admin/payments/_components/stats_card.html" with title="Average Balance" value="$"|add:stats.avg_balance|floatformat:2 color="warning" %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title="Active (7 days)" value=stats.active_balances color="info" %}
|
15
|
+
</div>
|
16
|
+
{% endwith %}
|
17
|
+
|
18
|
+
<div class="grid grid-cols-3 gap-4">
|
19
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
20
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Balance Distribution</h4>
|
21
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
22
|
+
<li>πΈ Empty: <span class="font-medium text-base-600 dark:text-base-400">{{ balance_stats.zero_balances|default:0 }}</span></li>
|
23
|
+
<li>πͺ Low ($0-10): <span class="font-medium text-yellow-600 dark:text-yellow-400">{{ balance_stats.low_balances|default:0 }}</span></li>
|
24
|
+
<li>π° Medium ($10-100): <span class="font-medium text-green-600 dark:text-green-400">{{ balance_stats.medium_balances|default:0 }}</span></li>
|
25
|
+
<li>π High ($100-1000): <span class="font-medium text-blue-600 dark:text-blue-400">{{ balance_stats.high_balances|default:0 }}</span></li>
|
26
|
+
<li>π Whale ($1000+): <span class="font-medium text-purple-600 dark:text-purple-400">{{ balance_stats.whale_balances|default:0 }}</span></li>
|
27
|
+
</ul>
|
28
|
+
</div>
|
29
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
30
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π¨ Alerts</h4>
|
31
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
32
|
+
<li>β οΈ Negative: <span class="font-medium text-red-600 dark:text-red-400">{{ balance_stats.negative_balances|default:0 }}</span></li>
|
33
|
+
<li>π Reserved: <span class="font-medium text-orange-600 dark:text-orange-400">${{ balance_stats.total_reserved|floatformat:2|default:0 }}</span></li>
|
34
|
+
</ul>
|
35
|
+
</div>
|
36
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
37
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π Top Balances</h4>
|
38
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
39
|
+
{% for balance in balance_stats.top_balances %}
|
40
|
+
<li><span class="font-semibold text-primary-600 dark:text-primary-400">{{ balance.user.username|truncatechars:15 }}:</span> ${{ balance.balance_usd|floatformat:2 }}</li>
|
41
|
+
{% empty %}
|
42
|
+
<li class="text-font-subtle-light dark:text-font-subtle-dark">No balances available</li>
|
43
|
+
{% endfor %}
|
44
|
+
</ul>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
{% endif %}
|
48
|
+
|
49
|
+
{{ block.super }}
|
50
|
+
{% endblock %}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
2
|
+
{% load static %}
|
3
|
+
|
4
|
+
{% block result_list %}
|
5
|
+
<!-- Currency Statistics Dashboard -->
|
6
|
+
{% if currency_stats %}
|
7
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="Currency Overview" icon="π" %}
|
8
|
+
|
9
|
+
{% with stats=currency_stats %}
|
10
|
+
<div class="grid grid-cols-4 gap-4 mb-4">
|
11
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Currencies" value=stats.total_currencies color="primary" %}
|
12
|
+
{% include "admin/payments/_components/stats_card.html" with title="Provider Mappings" value=stats.enabled_provider_currencies color="success" %}
|
13
|
+
{% include "admin/payments/_components/stats_card.html" with title="With USD Rates" value=stats.currencies_with_rates color="warning" %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title="Rate Coverage" value=stats.rate_coverage|floatformat:1|add:"%" color="info" %}
|
15
|
+
</div>
|
16
|
+
{% endwith %}
|
17
|
+
|
18
|
+
<div class="grid grid-cols-2 gap-4">
|
19
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
20
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Currency Types</h4>
|
21
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
22
|
+
<li>π΅ Fiat: <span class="font-medium text-primary-600 dark:text-primary-400">{{ stats.fiat_count|default:0 }}</span></li>
|
23
|
+
<li>βΏ Crypto: <span class="font-medium text-warning-600 dark:text-warning-400">{{ stats.crypto_count|default:0 }}</span></li>
|
24
|
+
</ul>
|
25
|
+
</div>
|
26
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
27
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π Top Supported</h4>
|
28
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
29
|
+
{% for currency in stats.top_currencies %}
|
30
|
+
<li><span class="font-semibold text-primary-600 dark:text-primary-400">{{ currency.code }}:</span> {{ currency.provider_count }} provider{{ currency.provider_count|pluralize }}</li>
|
31
|
+
{% empty %}
|
32
|
+
<li class="text-font-subtle-light dark:text-font-subtle-dark">No provider data available</li>
|
33
|
+
{% endfor %}
|
34
|
+
</ul>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
{% endif %}
|
38
|
+
|
39
|
+
{{ block.super }}
|
40
|
+
{% endblock %}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
2
|
+
{% load static %}
|
3
|
+
|
4
|
+
{% block result_list %}
|
5
|
+
<!-- Payment Statistics Dashboard -->
|
6
|
+
{% if payment_stats %}
|
7
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="Payment Overview" icon="π³" %}
|
8
|
+
|
9
|
+
{% with stats=payment_stats %}
|
10
|
+
<div class="grid grid-cols-4 gap-4 mb-4">
|
11
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Payments" value=stats.total_payments color="primary" %}
|
12
|
+
{% include "admin/payments/_components/stats_card.html" with title="Completed" value=stats.status_stats.completed|default:0 color="success" %}
|
13
|
+
{% include "admin/payments/_components/stats_card.html" with title="Pending" value=stats.status_stats.pending|default:0 color="warning" %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title="Failed" value=stats.status_stats.failed|default:0 color="error" %}
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="grid grid-cols-3 gap-4">
|
18
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
19
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π° Financial</h4>
|
20
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
21
|
+
<li>Total Volume: <span class="font-medium text-primary-600 dark:text-primary-400">${{ stats.total_amount|floatformat:2|default:0 }}</span></li>
|
22
|
+
<li>Completed: <span class="font-medium text-success-600 dark:text-success-400">${{ stats.completed_amount|floatformat:2|default:0 }}</span></li>
|
23
|
+
<li>Success Rate: <span class="font-medium text-info-600 dark:text-info-400">{{ stats.success_rate|floatformat:1|default:0 }}%</span></li>
|
24
|
+
</ul>
|
25
|
+
</div>
|
26
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
27
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π Recent (24h)</h4>
|
28
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
29
|
+
<li>Payments: <span class="font-medium text-primary-600 dark:text-primary-400">{{ stats.recent_payments|default:0 }}</span></li>
|
30
|
+
<li>Volume: <span class="font-medium text-success-600 dark:text-success-400">${{ stats.recent_amount|floatformat:2|default:0 }}</span></li>
|
31
|
+
</ul>
|
32
|
+
</div>
|
33
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
34
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π Providers</h4>
|
35
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
36
|
+
{% for provider in stats.provider_stats %}
|
37
|
+
<li>{{ provider.provider|title }}: <span class="font-medium text-primary-600 dark:text-primary-400">{{ provider.count }}</span></li>
|
38
|
+
{% empty %}
|
39
|
+
<li class="text-font-subtle-light dark:text-font-subtle-dark">No provider data</li>
|
40
|
+
{% endfor %}
|
41
|
+
</ul>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
{% endwith %}
|
45
|
+
{% endif %}
|
46
|
+
|
47
|
+
{{ block.super }}
|
48
|
+
{% endblock %}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
2
|
+
{% load static %}
|
3
|
+
|
4
|
+
{% block result_list %}
|
5
|
+
<!-- Subscription Statistics Dashboard -->
|
6
|
+
{% if subscription_stats %}
|
7
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="Subscriptions Overview" icon="π" %}
|
8
|
+
|
9
|
+
{% with stats=subscription_stats %}
|
10
|
+
<div class="grid grid-cols-4 gap-4 mb-4">
|
11
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Subscriptions" value=stats.total_subscriptions color="primary" %}
|
12
|
+
{% include "admin/payments/_components/stats_card.html" with title="Active" value=stats.active_subscriptions color="success" %}
|
13
|
+
{% include "admin/payments/_components/stats_card.html" with title="Expiring Soon" value=stats.expiring_subscriptions color="warning" %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title="Monthly Revenue" value="$"|add:stats.monthly_revenue|floatformat:2 color="info" %}
|
15
|
+
</div>
|
16
|
+
{% endwith %}
|
17
|
+
{% endif %}
|
18
|
+
|
19
|
+
<div class="grid grid-cols-3 gap-4">
|
20
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
21
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Subscription Tiers</h4>
|
22
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
23
|
+
<li>π₯ Basic: <span class="font-medium text-base-600 dark:text-base-400">{{ stats.basic_subs|default:0 }}</span></li>
|
24
|
+
<li>π₯ Premium: <span class="font-medium text-blue-600 dark:text-blue-400">{{ stats.premium_subs|default:0 }}</span></li>
|
25
|
+
<li>π₯ Enterprise: <span class="font-medium text-purple-600 dark:text-purple-400">{{ stats.enterprise_subs|default:0 }}</span></li>
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
29
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Status Distribution</h4>
|
30
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
31
|
+
<li>β
Active: <span class="font-medium text-success-600 dark:text-success-400">{{ stats.active_count|default:0 }}</span></li>
|
32
|
+
<li>βΈοΈ Paused: <span class="font-medium text-warning-600 dark:text-warning-400">{{ stats.paused_count|default:0 }}</span></li>
|
33
|
+
<li>β Cancelled: <span class="font-medium text-error-600 dark:text-error-400">{{ stats.cancelled_count|default:0 }}</span></li>
|
34
|
+
<li>β° Expired: <span class="font-medium text-base-600 dark:text-base-400">{{ stats.expired_count|default:0 }}</span></li>
|
35
|
+
</ul>
|
36
|
+
</div>
|
37
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
38
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">π Metrics</h4>
|
39
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
40
|
+
<li>π New (30d): <span class="font-medium text-success-600 dark:text-success-400">{{ stats.new_subs_30d|default:0 }}</span></li>
|
41
|
+
<li>π Churn Rate: <span class="font-medium text-warning-600 dark:text-warning-400">{{ stats.churn_rate|floatformat:1|default:0 }}%</span></li>
|
42
|
+
<li>π° ARPU: <span class="font-medium text-primary-600 dark:text-primary-400">${{ stats.arpu|floatformat:2|default:0 }}</span></li>
|
43
|
+
</ul>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
{{ block.super }}
|
48
|
+
{% endblock %}
|
django_cfg/apps/payments/urls.py
CHANGED
@@ -11,7 +11,7 @@ from rest_framework_nested import routers
|
|
11
11
|
from .views.api import (
|
12
12
|
PaymentViewSet, UserPaymentViewSet, PaymentCreateView, PaymentStatusView,
|
13
13
|
UserBalanceViewSet, TransactionViewSet, UserTransactionViewSet,
|
14
|
-
CurrencyViewSet, NetworkViewSet, ProviderCurrencyViewSet,
|
14
|
+
CurrencyViewSet, NetworkViewSet, ProviderCurrencyViewSet, CurrencyRatesView, SupportedCurrenciesView,
|
15
15
|
SubscriptionViewSet, UserSubscriptionViewSet, EndpointGroupViewSet, TariffViewSet,
|
16
16
|
APIKeyViewSet, UserAPIKeyViewSet, APIKeyCreateView, APIKeyValidateView,
|
17
17
|
UniversalWebhookView, webhook_health_check, webhook_stats, supported_providers,
|
@@ -60,7 +60,8 @@ urlpatterns = [
|
|
60
60
|
path('payments/create/', PaymentCreateView.as_view(), name='payment-create'),
|
61
61
|
path('payments/status/<uuid:pk>/', PaymentStatusView.as_view(), name='payment-status'),
|
62
62
|
|
63
|
-
|
63
|
+
# Note: currencies/convert/ is handled by CurrencyViewSet action
|
64
|
+
# path('currencies/convert/', CurrencyConversionView.as_view(), name='currency-convert'),
|
64
65
|
path('currencies/rates/', CurrencyRatesView.as_view(), name='currency-rates'),
|
65
66
|
path('currencies/supported/', SupportedCurrenciesView.as_view(), name='currencies-supported'),
|
66
67
|
|
@@ -39,7 +39,7 @@ webhook_events_router.register(r'events', AdminWebhookEventViewSet, basename='ad
|
|
39
39
|
|
40
40
|
# Public API router (no authentication required)
|
41
41
|
public_router = DefaultRouter()
|
42
|
-
public_router.register(r'
|
42
|
+
public_router.register(r'webhook-test', WebhookTestViewSet, basename='webhook-test')
|
43
43
|
|
44
44
|
urlpatterns = [
|
45
45
|
# Template Views
|