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
@@ -0,0 +1,239 @@
|
|
1
|
+
"""
|
2
|
+
Management commands collector for django-cfg.
|
3
|
+
|
4
|
+
Collects and groups all available Django management commands.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Dict, List, Optional, Set
|
10
|
+
from collections import defaultdict
|
11
|
+
|
12
|
+
try:
|
13
|
+
from django.core.management import get_commands
|
14
|
+
from django.apps import apps
|
15
|
+
DJANGO_AVAILABLE = True
|
16
|
+
except ImportError:
|
17
|
+
DJANGO_AVAILABLE = False
|
18
|
+
|
19
|
+
|
20
|
+
class CommandsCollector:
|
21
|
+
"""
|
22
|
+
Collects and organizes Django management commands by source.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self):
|
26
|
+
"""Initialize commands collector."""
|
27
|
+
self.django_cfg_path = Path(__file__).parent.parent.parent
|
28
|
+
self.commands_cache = None
|
29
|
+
|
30
|
+
def get_all_commands(self) -> Dict[str, Dict[str, List[str]]]:
|
31
|
+
"""
|
32
|
+
Get all available commands grouped by source.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Dictionary with command groups:
|
36
|
+
{
|
37
|
+
'django_cfg_core': {'Core Commands': [...]},
|
38
|
+
'django_cfg_apps': {'App Name': [...], ...},
|
39
|
+
'project_commands': {'Project Commands': [...]}
|
40
|
+
}
|
41
|
+
"""
|
42
|
+
if self.commands_cache is not None:
|
43
|
+
return self.commands_cache
|
44
|
+
|
45
|
+
commands = {
|
46
|
+
'django_cfg_core': {},
|
47
|
+
'django_cfg_apps': {},
|
48
|
+
'project_commands': {}
|
49
|
+
}
|
50
|
+
|
51
|
+
# Get Django-CFG core commands
|
52
|
+
core_commands = self._get_django_cfg_core_commands()
|
53
|
+
if core_commands:
|
54
|
+
commands['django_cfg_core']['Core Commands'] = sorted(core_commands)
|
55
|
+
|
56
|
+
# Get Django-CFG app commands
|
57
|
+
app_commands = self._get_django_cfg_app_commands()
|
58
|
+
for app_name, app_cmds in app_commands.items():
|
59
|
+
if app_cmds:
|
60
|
+
commands['django_cfg_apps'][app_name] = sorted(app_cmds)
|
61
|
+
|
62
|
+
# Get project commands (if Django is available)
|
63
|
+
if DJANGO_AVAILABLE:
|
64
|
+
project_commands = self._get_project_commands()
|
65
|
+
if project_commands:
|
66
|
+
commands['project_commands']['Project Commands'] = sorted(project_commands)
|
67
|
+
|
68
|
+
self.commands_cache = commands
|
69
|
+
return commands
|
70
|
+
|
71
|
+
def _get_django_cfg_core_commands(self) -> List[str]:
|
72
|
+
"""Get Django-CFG core management commands."""
|
73
|
+
commands = []
|
74
|
+
core_commands_path = self.django_cfg_path / "management" / "commands"
|
75
|
+
|
76
|
+
if core_commands_path.exists():
|
77
|
+
for file_path in core_commands_path.glob("*.py"):
|
78
|
+
if file_path.name != "__init__.py":
|
79
|
+
command_name = file_path.stem
|
80
|
+
commands.append(command_name)
|
81
|
+
|
82
|
+
return commands
|
83
|
+
|
84
|
+
def _get_django_cfg_app_commands(self) -> Dict[str, List[str]]:
|
85
|
+
"""Get Django-CFG app-specific management commands."""
|
86
|
+
app_commands = defaultdict(list)
|
87
|
+
apps_path = self.django_cfg_path / "apps"
|
88
|
+
|
89
|
+
if not apps_path.exists():
|
90
|
+
return dict(app_commands)
|
91
|
+
|
92
|
+
for app_dir in apps_path.iterdir():
|
93
|
+
if app_dir.is_dir() and not app_dir.name.startswith('.') and app_dir.name != '__pycache__':
|
94
|
+
# Skip @old directory
|
95
|
+
if app_dir.name.startswith('@'):
|
96
|
+
continue
|
97
|
+
|
98
|
+
commands_path = app_dir / "management" / "commands"
|
99
|
+
if commands_path.exists():
|
100
|
+
app_name = app_dir.name.title()
|
101
|
+
|
102
|
+
for file_path in commands_path.glob("*.py"):
|
103
|
+
if file_path.name != "__init__.py":
|
104
|
+
command_name = file_path.stem
|
105
|
+
app_commands[app_name].append(command_name)
|
106
|
+
|
107
|
+
return dict(app_commands)
|
108
|
+
|
109
|
+
def _get_project_commands(self) -> List[str]:
|
110
|
+
"""Get project-specific management commands (excluding Django-CFG)."""
|
111
|
+
if not DJANGO_AVAILABLE:
|
112
|
+
return []
|
113
|
+
|
114
|
+
try:
|
115
|
+
all_commands = get_commands()
|
116
|
+
django_cfg_commands = set()
|
117
|
+
|
118
|
+
# Collect all Django-CFG commands
|
119
|
+
core_commands = self._get_django_cfg_core_commands()
|
120
|
+
django_cfg_commands.update(core_commands)
|
121
|
+
|
122
|
+
app_commands = self._get_django_cfg_app_commands()
|
123
|
+
for app_cmds in app_commands.values():
|
124
|
+
django_cfg_commands.update(app_cmds)
|
125
|
+
|
126
|
+
# Filter out Django-CFG commands and Django built-ins
|
127
|
+
django_builtin_commands = {
|
128
|
+
'check', 'compilemessages', 'createcachetable', 'dbshell',
|
129
|
+
'diffsettings', 'dumpdata', 'flush', 'inspectdb', 'loaddata',
|
130
|
+
'makemessages', 'makemigrations', 'migrate', 'optimizemigration',
|
131
|
+
'runserver', 'shell', 'showmigrations', 'sqlflush', 'sqlmigrate',
|
132
|
+
'sqlsequencereset', 'squashmigrations', 'startapp', 'startproject',
|
133
|
+
'test', 'testserver', 'collectstatic', 'findstatic', 'clearsessions',
|
134
|
+
'createsuperuser', 'changepassword'
|
135
|
+
}
|
136
|
+
|
137
|
+
project_commands = []
|
138
|
+
for cmd_name in all_commands.keys():
|
139
|
+
if (cmd_name not in django_cfg_commands and
|
140
|
+
cmd_name not in django_builtin_commands):
|
141
|
+
project_commands.append(cmd_name)
|
142
|
+
|
143
|
+
return project_commands
|
144
|
+
|
145
|
+
except Exception:
|
146
|
+
return []
|
147
|
+
|
148
|
+
def get_command_description(self, command_name: str) -> Optional[str]:
|
149
|
+
"""
|
150
|
+
Get command description from its help text.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
command_name: Name of the command
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
Command description or None if not available
|
157
|
+
"""
|
158
|
+
if not DJANGO_AVAILABLE:
|
159
|
+
return None
|
160
|
+
|
161
|
+
try:
|
162
|
+
from django.core.management import load_command_class
|
163
|
+
from django.core.management.base import CommandError
|
164
|
+
|
165
|
+
try:
|
166
|
+
command = load_command_class(None, command_name)
|
167
|
+
return getattr(command, 'help', None) or None
|
168
|
+
except (CommandError, ImportError, AttributeError):
|
169
|
+
return None
|
170
|
+
except Exception:
|
171
|
+
return None
|
172
|
+
|
173
|
+
def get_commands_with_descriptions(self) -> Dict[str, Dict[str, Dict[str, Optional[str]]]]:
|
174
|
+
"""
|
175
|
+
Get all commands with their descriptions.
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
Dictionary with commands and descriptions:
|
179
|
+
{
|
180
|
+
'django_cfg_core': {'Core Commands': {'cmd': 'description', ...}},
|
181
|
+
'django_cfg_apps': {'App Name': {'cmd': 'description', ...}, ...},
|
182
|
+
'project_commands': {'Project Commands': {'cmd': 'description', ...}}
|
183
|
+
}
|
184
|
+
"""
|
185
|
+
all_commands = self.get_all_commands()
|
186
|
+
commands_with_desc = {}
|
187
|
+
|
188
|
+
for category, groups in all_commands.items():
|
189
|
+
commands_with_desc[category] = {}
|
190
|
+
|
191
|
+
for group_name, commands in groups.items():
|
192
|
+
commands_with_desc[category][group_name] = {}
|
193
|
+
|
194
|
+
for cmd in commands:
|
195
|
+
desc = self.get_command_description(cmd)
|
196
|
+
commands_with_desc[category][group_name][cmd] = desc
|
197
|
+
|
198
|
+
return commands_with_desc
|
199
|
+
|
200
|
+
|
201
|
+
# Global instance
|
202
|
+
_commands_collector = CommandsCollector()
|
203
|
+
|
204
|
+
|
205
|
+
def get_all_commands() -> Dict[str, Dict[str, List[str]]]:
|
206
|
+
"""
|
207
|
+
Get all available Django management commands grouped by source.
|
208
|
+
|
209
|
+
Returns:
|
210
|
+
Dictionary with command groups
|
211
|
+
"""
|
212
|
+
return _commands_collector.get_all_commands()
|
213
|
+
|
214
|
+
|
215
|
+
def get_commands_with_descriptions() -> Dict[str, Dict[str, Dict[str, Optional[str]]]]:
|
216
|
+
"""
|
217
|
+
Get all commands with their descriptions.
|
218
|
+
|
219
|
+
Returns:
|
220
|
+
Dictionary with commands and descriptions
|
221
|
+
"""
|
222
|
+
return _commands_collector.get_commands_with_descriptions()
|
223
|
+
|
224
|
+
|
225
|
+
def get_command_count() -> int:
|
226
|
+
"""
|
227
|
+
Get total count of available commands.
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
Total number of commands
|
231
|
+
"""
|
232
|
+
all_commands = get_all_commands()
|
233
|
+
total = 0
|
234
|
+
|
235
|
+
for category in all_commands.values():
|
236
|
+
for commands in category.values():
|
237
|
+
total += len(commands)
|
238
|
+
|
239
|
+
return total
|
@@ -0,0 +1,15 @@
|
|
1
|
+
"""
|
2
|
+
Django CFG Display System.
|
3
|
+
|
4
|
+
Modular, class-based display system for startup information.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .base import BaseDisplayManager
|
8
|
+
from .startup import StartupDisplayManager
|
9
|
+
from .ngrok import NgrokDisplayManager
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"BaseDisplayManager",
|
13
|
+
"StartupDisplayManager",
|
14
|
+
"NgrokDisplayManager",
|
15
|
+
]
|
@@ -0,0 +1,157 @@
|
|
1
|
+
"""
|
2
|
+
Base display manager for Django CFG startup information.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional, Dict, Any, List
|
6
|
+
from rich.console import Console
|
7
|
+
from rich.table import Table
|
8
|
+
from rich.panel import Panel
|
9
|
+
from rich.columns import Columns
|
10
|
+
from rich.text import Text
|
11
|
+
from rich.align import Align
|
12
|
+
|
13
|
+
# Panel width configuration - use fixed widths for consistent layout
|
14
|
+
CONSOLE_WIDTH = 120 # Console width that fits most terminals
|
15
|
+
MAIN_PANEL_WIDTH = CONSOLE_WIDTH # Full width panels
|
16
|
+
HALF_PANEL_WIDTH = (CONSOLE_WIDTH - 10) // 2 # 50% width for columns (minus borders/padding)
|
17
|
+
|
18
|
+
|
19
|
+
class BaseDisplayManager:
|
20
|
+
"""Base class for all display managers."""
|
21
|
+
|
22
|
+
def __init__(self, config=None):
|
23
|
+
"""Initialize display manager with config."""
|
24
|
+
self.config = config or self._get_current_config()
|
25
|
+
self.console = Console()
|
26
|
+
|
27
|
+
def _get_current_config(self):
|
28
|
+
"""Get current Django CFG configuration."""
|
29
|
+
try:
|
30
|
+
from django_cfg.core.config import get_current_config
|
31
|
+
return get_current_config()
|
32
|
+
except Exception:
|
33
|
+
return None
|
34
|
+
|
35
|
+
def get_base_url(self, *paths) -> str:
|
36
|
+
"""Get base URL for API endpoints with optional path components."""
|
37
|
+
if self.config and hasattr(self.config, 'api_url'):
|
38
|
+
base = self.config.api_url.rstrip('/')
|
39
|
+
else:
|
40
|
+
base = "http://localhost:8000"
|
41
|
+
|
42
|
+
if paths:
|
43
|
+
# Join all path components
|
44
|
+
path_parts = []
|
45
|
+
for path in paths:
|
46
|
+
if path:
|
47
|
+
path_parts.append(str(path).strip('/'))
|
48
|
+
|
49
|
+
if path_parts:
|
50
|
+
return f"{base}/{'/'.join(path_parts)}/"
|
51
|
+
|
52
|
+
return base
|
53
|
+
|
54
|
+
def get_environment_style(self):
|
55
|
+
"""Get environment styling (panel_style, env_emoji, env_color)."""
|
56
|
+
if not self.config:
|
57
|
+
return "yellow", "🧪", "yellow"
|
58
|
+
|
59
|
+
if self.config.is_development:
|
60
|
+
return "green", "🚧", "green"
|
61
|
+
elif self.config.is_production:
|
62
|
+
return "red", "🚀", "red"
|
63
|
+
else:
|
64
|
+
return "yellow", "🧪", "yellow"
|
65
|
+
|
66
|
+
def get_version(self) -> str:
|
67
|
+
"""Get Django CFG version."""
|
68
|
+
try:
|
69
|
+
from django_cfg import __version__
|
70
|
+
return __version__
|
71
|
+
except ImportError:
|
72
|
+
return "unknown"
|
73
|
+
|
74
|
+
def create_panel(self, content, title: str, border_style: str = "blue",
|
75
|
+
width: Optional[int] = None, expand: bool = False) -> Panel:
|
76
|
+
"""Create a standardized panel with fixed width by default."""
|
77
|
+
# Use MAIN_PANEL_WIDTH by default for consistent layout
|
78
|
+
panel_width = width if width is not None else MAIN_PANEL_WIDTH
|
79
|
+
|
80
|
+
return Panel(
|
81
|
+
content,
|
82
|
+
title=title,
|
83
|
+
border_style=border_style,
|
84
|
+
width=panel_width,
|
85
|
+
expand=expand,
|
86
|
+
padding=(1, 2)
|
87
|
+
)
|
88
|
+
|
89
|
+
def create_full_width_panel(self, content, title: str, border_style: str = "blue") -> Panel:
|
90
|
+
"""Create a panel that spans the full width (same as two columns)."""
|
91
|
+
# Wrap in a table to match the width of two-column layout
|
92
|
+
wrapper_table = Table(show_header=False, box=None, padding=(0, 0), width=MAIN_PANEL_WIDTH)
|
93
|
+
wrapper_table.add_column("Content", width=MAIN_PANEL_WIDTH, justify="left")
|
94
|
+
|
95
|
+
panel = Panel(
|
96
|
+
content,
|
97
|
+
title=title,
|
98
|
+
border_style=border_style,
|
99
|
+
expand=True,
|
100
|
+
padding=(1, 2)
|
101
|
+
)
|
102
|
+
|
103
|
+
wrapper_table.add_row(panel)
|
104
|
+
return wrapper_table
|
105
|
+
|
106
|
+
def create_table(self, title: str = None, show_header: bool = False) -> Table:
|
107
|
+
"""Create a standardized table."""
|
108
|
+
table = Table(title=title, show_header=show_header, box=None)
|
109
|
+
return table
|
110
|
+
|
111
|
+
def print_panel(self, panel: Panel, centered: bool = False):
|
112
|
+
"""Print a panel, optionally centered."""
|
113
|
+
if centered:
|
114
|
+
self.console.print(Align.center(panel))
|
115
|
+
else:
|
116
|
+
self.console.print(panel)
|
117
|
+
|
118
|
+
def print_columns(self, panels: List[Panel], equal: bool = True, expand: bool = True):
|
119
|
+
"""Print panels in columns."""
|
120
|
+
if panels:
|
121
|
+
self.console.print(Columns(panels, equal=equal, expand=expand))
|
122
|
+
|
123
|
+
def print_two_column_table(self, left_content: str, right_content: str,
|
124
|
+
left_title: str = "", right_title: str = "",
|
125
|
+
left_style: str = "blue", right_style: str = "green"):
|
126
|
+
"""Print content in a proper 50/50 two-column layout with panels."""
|
127
|
+
# Create panels that will expand to fill table cells
|
128
|
+
left_panel = Panel(
|
129
|
+
left_content,
|
130
|
+
title=left_title,
|
131
|
+
border_style=left_style,
|
132
|
+
expand=True,
|
133
|
+
padding=(1, 1)
|
134
|
+
)
|
135
|
+
|
136
|
+
right_panel = Panel(
|
137
|
+
right_content,
|
138
|
+
title=right_title,
|
139
|
+
border_style=right_style,
|
140
|
+
expand=True,
|
141
|
+
padding=(1, 1)
|
142
|
+
)
|
143
|
+
|
144
|
+
# Use a table to force exact positioning
|
145
|
+
wrapper_table = Table(show_header=False, box=None, padding=(0, 0), width=MAIN_PANEL_WIDTH)
|
146
|
+
wrapper_table.add_column("Left", width=HALF_PANEL_WIDTH, justify="left")
|
147
|
+
wrapper_table.add_column("Right", width=HALF_PANEL_WIDTH, justify="left")
|
148
|
+
|
149
|
+
# Add panels as table cells
|
150
|
+
wrapper_table.add_row(left_panel, right_panel)
|
151
|
+
|
152
|
+
self.console.print(wrapper_table)
|
153
|
+
|
154
|
+
def print_spacing(self, lines: int = 1):
|
155
|
+
"""Print empty lines for spacing."""
|
156
|
+
for _ in range(lines):
|
157
|
+
self.console.print()
|
@@ -0,0 +1,164 @@
|
|
1
|
+
"""
|
2
|
+
Ngrok display manager for Django CFG.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional
|
6
|
+
from .base import BaseDisplayManager, MAIN_PANEL_WIDTH
|
7
|
+
|
8
|
+
|
9
|
+
class NgrokDisplayManager(BaseDisplayManager):
|
10
|
+
"""Manager for displaying ngrok tunnel information."""
|
11
|
+
|
12
|
+
def display_tunnel_info(self, tunnel_url: str):
|
13
|
+
"""Display active ngrok tunnel information."""
|
14
|
+
try:
|
15
|
+
if not self._is_ngrok_configured():
|
16
|
+
return
|
17
|
+
|
18
|
+
ngrok_service = self._get_ngrok_service()
|
19
|
+
if not ngrok_service:
|
20
|
+
return
|
21
|
+
|
22
|
+
# Create ngrok info table
|
23
|
+
ngrok_table = self.create_table()
|
24
|
+
ngrok_table.add_column("Key", style="cyan", no_wrap=True)
|
25
|
+
ngrok_table.add_column("Value", style="bright_white")
|
26
|
+
|
27
|
+
# Add tunnel information
|
28
|
+
ngrok_table.add_row("🌐 Tunnel URL:", tunnel_url)
|
29
|
+
|
30
|
+
# Add webhook URL example
|
31
|
+
webhook_url = ngrok_service.get_webhook_url()
|
32
|
+
ngrok_table.add_row("🔗 Webhook URL:", webhook_url)
|
33
|
+
|
34
|
+
# Add API URL
|
35
|
+
api_url = ngrok_service.get_api_url()
|
36
|
+
ngrok_table.add_row("🚀 API URL:", api_url)
|
37
|
+
|
38
|
+
# Add environment variables info
|
39
|
+
ngrok_table.add_row("📝 Env Variables:", "NGROK_URL, DJANGO_NGROK_URL set")
|
40
|
+
|
41
|
+
# Add domain info if configured
|
42
|
+
if self.config.ngrok.tunnel.domain:
|
43
|
+
ngrok_table.add_row("🏷️ Custom Domain:", self.config.ngrok.tunnel.domain)
|
44
|
+
|
45
|
+
# Add auth info
|
46
|
+
if self.config.ngrok.auth.get_authtoken():
|
47
|
+
ngrok_table.add_row("🔐 Auth Token:", "✅ Configured")
|
48
|
+
else:
|
49
|
+
ngrok_table.add_row("🔐 Auth Token:", "❌ Not configured (limited features)")
|
50
|
+
|
51
|
+
# Create panel with ngrok info
|
52
|
+
ngrok_panel = self.create_panel(
|
53
|
+
ngrok_table,
|
54
|
+
title="🚇 [bold green]Ngrok Tunnel Active[/bold green]",
|
55
|
+
border_style="green",
|
56
|
+
width=MAIN_PANEL_WIDTH
|
57
|
+
)
|
58
|
+
|
59
|
+
# Print the panel (not centered to respect width)
|
60
|
+
self.print_spacing()
|
61
|
+
self.console.print(ngrok_panel)
|
62
|
+
self.print_spacing()
|
63
|
+
|
64
|
+
except Exception:
|
65
|
+
# Silently fail - ngrok info is not critical
|
66
|
+
pass
|
67
|
+
|
68
|
+
def display_config_status(self):
|
69
|
+
"""Display ngrok configuration status when tunnel is not active."""
|
70
|
+
try:
|
71
|
+
if not self._is_ngrok_configured():
|
72
|
+
return
|
73
|
+
|
74
|
+
ngrok_service = self._get_ngrok_service()
|
75
|
+
if not ngrok_service:
|
76
|
+
return
|
77
|
+
|
78
|
+
# Create ngrok config status table
|
79
|
+
status_table = self.create_table()
|
80
|
+
status_table.add_column("Key", style="cyan", no_wrap=True)
|
81
|
+
status_table.add_column("Value", style="bright_white")
|
82
|
+
|
83
|
+
# Add configuration status
|
84
|
+
status_table.add_row("🔧 Configuration:", "✅ Enabled")
|
85
|
+
|
86
|
+
# Add auth status
|
87
|
+
if self.config.ngrok.auth.get_authtoken():
|
88
|
+
status_table.add_row("🔐 Auth Token:", "✅ Configured")
|
89
|
+
else:
|
90
|
+
status_table.add_row("🔐 Auth Token:", "❌ Not configured")
|
91
|
+
|
92
|
+
# Add domain info if configured
|
93
|
+
if self.config.ngrok.tunnel.domain:
|
94
|
+
status_table.add_row("🏷️ Custom Domain:", self.config.ngrok.tunnel.domain)
|
95
|
+
|
96
|
+
# Add auto-start status
|
97
|
+
if self.config.ngrok.auto_start:
|
98
|
+
status_table.add_row("🚀 Auto Start:", "✅ Enabled")
|
99
|
+
else:
|
100
|
+
status_table.add_row("🚀 Auto Start:", "❌ Disabled")
|
101
|
+
|
102
|
+
# Add usage hint
|
103
|
+
status_table.add_row("💡 Usage:", "Run 'pnpm manage.py runserver_ngrok' to start tunnel")
|
104
|
+
|
105
|
+
# Create panel with ngrok config status - full width
|
106
|
+
ngrok_panel = self.create_full_width_panel(
|
107
|
+
status_table,
|
108
|
+
title="🚇 [bold yellow]Ngrok Ready (Not Active)[/bold yellow]",
|
109
|
+
border_style="yellow"
|
110
|
+
)
|
111
|
+
|
112
|
+
# Print the panel (not centered to respect width)
|
113
|
+
self.print_spacing()
|
114
|
+
self.console.print(ngrok_panel)
|
115
|
+
self.print_spacing()
|
116
|
+
|
117
|
+
except Exception:
|
118
|
+
# Silently fail - ngrok config status is not critical
|
119
|
+
pass
|
120
|
+
|
121
|
+
def display_if_active(self):
|
122
|
+
"""Display ngrok information if configured and check if active."""
|
123
|
+
if not self._is_ngrok_configured():
|
124
|
+
return
|
125
|
+
|
126
|
+
ngrok_service = self._get_ngrok_service()
|
127
|
+
if not ngrok_service:
|
128
|
+
return
|
129
|
+
|
130
|
+
# Check if tunnel is active or available from environment
|
131
|
+
tunnel_url = ngrok_service.get_tunnel_url()
|
132
|
+
env_url = ngrok_service.get_tunnel_url_from_env()
|
133
|
+
|
134
|
+
# IMPORTANT: During Django startup, ngrok tunnel may not be active yet
|
135
|
+
# Only show active tunnel info if we actually have a tunnel URL
|
136
|
+
# Otherwise, show config status (ready but not active)
|
137
|
+
active_url = None
|
138
|
+
|
139
|
+
if tunnel_url:
|
140
|
+
# Active tunnel found from manager
|
141
|
+
active_url = tunnel_url
|
142
|
+
elif env_url:
|
143
|
+
# Environment URL found (tunnel was started in this process)
|
144
|
+
active_url = env_url
|
145
|
+
|
146
|
+
if active_url:
|
147
|
+
self.display_tunnel_info(active_url)
|
148
|
+
else:
|
149
|
+
self.display_config_status()
|
150
|
+
|
151
|
+
def _is_ngrok_configured(self) -> bool:
|
152
|
+
"""Check if ngrok is configured."""
|
153
|
+
return (self.config and
|
154
|
+
hasattr(self.config, 'ngrok') and
|
155
|
+
self.config.ngrok and
|
156
|
+
self.config.ngrok.enabled)
|
157
|
+
|
158
|
+
def _get_ngrok_service(self):
|
159
|
+
"""Get ngrok service instance."""
|
160
|
+
try:
|
161
|
+
from django_cfg.modules.django_ngrok import get_ngrok_service
|
162
|
+
return get_ngrok_service()
|
163
|
+
except Exception:
|
164
|
+
return None
|