django-cfg 1.2.29__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -9
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -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