django-cfg 1.4.9__py3-none-any.whl → 1.4.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.
Files changed (193) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/middleware/api_access.py +6 -2
  8. django_cfg/apps/payments/middleware/rate_limiting.py +2 -1
  9. django_cfg/apps/payments/middleware/usage_tracking.py +5 -1
  10. django_cfg/apps/payments/models/managers/api_key_managers.py +0 -1
  11. django_cfg/apps/payments/models/managers/subscription_managers.py +0 -1
  12. django_cfg/apps/payments/services/core/balance_service.py +5 -5
  13. django_cfg/apps/payments/services/core/subscription_service.py +1 -2
  14. django_cfg/apps/payments/views/api/balances.py +8 -7
  15. django_cfg/apps/payments/views/api/base.py +10 -6
  16. django_cfg/apps/payments/views/api/currencies.py +53 -10
  17. django_cfg/apps/payments/views/api/payments.py +3 -1
  18. django_cfg/apps/payments/views/api/subscriptions.py +2 -5
  19. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  20. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  21. django_cfg/apps/payments/views/overview/views.py +2 -1
  22. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  23. django_cfg/apps/urls.py +106 -45
  24. django_cfg/core/base/config_model.py +2 -2
  25. django_cfg/core/constants.py +1 -1
  26. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  27. django_cfg/core/generation/integration_generators/api.py +82 -41
  28. django_cfg/core/integration/display/startup.py +30 -22
  29. django_cfg/core/integration/url_integration.py +15 -16
  30. django_cfg/dashboard/sections/documentation.py +391 -0
  31. django_cfg/management/commands/check_endpoints.py +11 -160
  32. django_cfg/management/commands/check_settings.py +13 -265
  33. django_cfg/management/commands/clear_constance.py +13 -201
  34. django_cfg/management/commands/create_token.py +13 -321
  35. django_cfg/management/commands/generate_clients.py +23 -0
  36. django_cfg/management/commands/list_urls.py +13 -306
  37. django_cfg/management/commands/migrate_all.py +13 -126
  38. django_cfg/management/commands/migrator.py +13 -396
  39. django_cfg/management/commands/rundramatiq.py +15 -247
  40. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  41. django_cfg/management/commands/runserver_ngrok.py +15 -160
  42. django_cfg/management/commands/script.py +12 -488
  43. django_cfg/management/commands/show_config.py +12 -215
  44. django_cfg/management/commands/show_urls.py +12 -342
  45. django_cfg/management/commands/superuser.py +15 -295
  46. django_cfg/management/commands/task_clear.py +14 -217
  47. django_cfg/management/commands/task_status.py +13 -248
  48. django_cfg/management/commands/test_email.py +15 -86
  49. django_cfg/management/commands/test_telegram.py +14 -61
  50. django_cfg/management/commands/test_twilio.py +15 -105
  51. django_cfg/management/commands/tree.py +13 -383
  52. django_cfg/management/commands/validate_openapi.py +10 -0
  53. django_cfg/middleware/README.md +1 -1
  54. django_cfg/middleware/user_activity.py +3 -3
  55. django_cfg/models/__init__.py +2 -2
  56. django_cfg/models/api/drf/spectacular.py +6 -6
  57. django_cfg/models/django/__init__.py +2 -2
  58. django_cfg/models/django/openapi.py +238 -0
  59. django_cfg/models/django/{revolution.py → revolution_legacy.py} +8 -0
  60. django_cfg/modules/django_admin/management/__init__.py +0 -0
  61. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  62. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  63. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  64. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  65. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  66. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  67. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  68. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  69. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  70. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  71. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  72. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  73. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  74. django_cfg/modules/django_client/__init__.py +20 -0
  75. django_cfg/modules/django_client/apps.py +35 -0
  76. django_cfg/modules/django_client/core/__init__.py +56 -0
  77. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  78. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  79. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  80. django_cfg/modules/django_client/core/cli/main.py +235 -0
  81. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  82. django_cfg/modules/django_client/core/config/config.py +188 -0
  83. django_cfg/modules/django_client/core/config/group.py +101 -0
  84. django_cfg/modules/django_client/core/config/service.py +209 -0
  85. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  86. django_cfg/modules/django_client/core/generator/base.py +767 -0
  87. django_cfg/modules/django_client/core/generator/python.py +751 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  94. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  95. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  96. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  97. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  98. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  99. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  100. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  102. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  103. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  104. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  105. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  112. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  113. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  114. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  115. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  116. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  117. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  118. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  119. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  120. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  121. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  122. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  123. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  124. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  125. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  126. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  127. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  128. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  129. django_cfg/modules/django_client/core/ir/context.py +387 -0
  130. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  131. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  132. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  133. django_cfg/modules/django_client/core/parser/base.py +648 -0
  134. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  135. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  136. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  137. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  138. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  139. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  140. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  141. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  142. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  143. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  144. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  145. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  146. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  147. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  148. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  149. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  150. django_cfg/modules/django_client/management/__init__.py +3 -0
  151. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  152. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  153. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  154. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  155. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  156. django_cfg/modules/django_client/urls.py +72 -0
  157. django_cfg/modules/django_email/management/__init__.py +0 -0
  158. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  159. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  160. django_cfg/modules/django_logging/django_logger.py +6 -6
  161. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  162. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  164. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  165. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  166. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  167. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  168. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  169. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  170. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  171. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  172. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  173. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  174. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  176. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  177. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  178. django_cfg/modules/django_unfold/dashboard.py +1 -1
  179. django_cfg/pyproject.toml +2 -6
  180. django_cfg/registry/third_party.py +5 -7
  181. django_cfg/routing/callbacks.py +1 -1
  182. django_cfg/static/admin/css/prose-unfold.css +666 -0
  183. django_cfg/templates/admin/index.html +8 -0
  184. django_cfg/templates/admin/index_new.html +13 -0
  185. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  186. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  187. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  188. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  189. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/RECORD +192 -71
  190. django_cfg/management/commands/generate.py +0 -107
  191. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  192. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  193. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,328 @@
1
+ """
2
+ Create Token Command for Django Config Toolkit
3
+ Generate API tokens and authentication tokens.
4
+ """
5
+
6
+ import os
7
+ import secrets
8
+ import string
9
+ from pathlib import Path
10
+ from django.core.management.base import BaseCommand
11
+ from django.contrib.auth import get_user_model
12
+ from django.conf import settings
13
+ import questionary
14
+ from datetime import datetime, timedelta
15
+ from django_cfg.modules.django_logging import get_logger
16
+
17
+ User = get_user_model()
18
+
19
+
20
+ logger = get_logger('create_token')
21
+
22
+ class Command(BaseCommand):
23
+ # Web execution metadata
24
+ web_executable = False
25
+ requires_input = True
26
+ is_destructive = False
27
+
28
+ help = 'Create API tokens and authentication tokens'
29
+
30
+ def add_arguments(self, parser):
31
+ parser.add_argument(
32
+ '--user',
33
+ type=str,
34
+ help='Username to create token for'
35
+ )
36
+ parser.add_argument(
37
+ '--type',
38
+ type=str,
39
+ choices=['api', 'auth', 'secret'],
40
+ help='Type of token to create'
41
+ )
42
+ parser.add_argument(
43
+ '--length',
44
+ type=int,
45
+ default=32,
46
+ help='Token length (default: 32)'
47
+ )
48
+ parser.add_argument(
49
+ '--expires',
50
+ type=int,
51
+ help='Token expiration in days'
52
+ )
53
+
54
+ def handle(self, *args, **options):
55
+ logger.info("Starting create_token command")
56
+ if options['user'] and options['type']:
57
+ self.create_token_for_user(
58
+ username=options['user'],
59
+ token_type=options['type'],
60
+ length=options['length'],
61
+ expires_days=options['expires']
62
+ )
63
+ else:
64
+ self.show_interactive_menu()
65
+
66
+ def show_interactive_menu(self):
67
+ """Show interactive menu with token creation options"""
68
+ self.stdout.write(self.style.SUCCESS('\n🔑 Token Creation Tool - Django Config Toolkit\n'))
69
+
70
+ choices = [
71
+ questionary.Choice('🔑 Create API Token', value='api'),
72
+ questionary.Choice('🔐 Create Auth Token', value='auth'),
73
+ questionary.Choice('🔒 Create Secret Key', value='secret'),
74
+ questionary.Choice('👤 Create Token for User', value='user'),
75
+ questionary.Choice('📝 Generate Django Secret Key', value='django_secret'),
76
+ questionary.Choice('❌ Exit', value='exit')
77
+ ]
78
+
79
+ choice = questionary.select(
80
+ 'Select token type:',
81
+ choices=choices
82
+ ).ask()
83
+
84
+ if choice == 'api':
85
+ self.create_api_token()
86
+ elif choice == 'auth':
87
+ self.create_auth_token()
88
+ elif choice == 'secret':
89
+ self.create_secret_key()
90
+ elif choice == 'user':
91
+ self.create_token_for_user_interactive()
92
+ elif choice == 'django_secret':
93
+ self.generate_django_secret_key()
94
+ elif choice == 'exit':
95
+ self.stdout.write('Goodbye! 👋')
96
+ return
97
+
98
+ def create_api_token(self):
99
+ """Create API token"""
100
+ self.stdout.write(self.style.SUCCESS('🔑 Creating API Token...'))
101
+
102
+ # Get token details
103
+ token_name = questionary.text('Token name:').ask()
104
+ if not token_name:
105
+ self.stdout.write(self.style.ERROR('❌ Token name is required'))
106
+ return
107
+
108
+ token_length = questionary.select(
109
+ 'Token length:',
110
+ choices=['32', '64', '128', '256']
111
+ ).ask()
112
+
113
+ expires = questionary.select(
114
+ 'Token expiration:',
115
+ choices=['Never', '30 days', '90 days', '1 year']
116
+ ).ask()
117
+
118
+ # Generate token
119
+ token = self.generate_token(int(token_length))
120
+
121
+ # Calculate expiration
122
+ expiration_date = None
123
+ if expires != 'Never':
124
+ days_map = {
125
+ '30 days': 30,
126
+ '90 days': 90,
127
+ '1 year': 365
128
+ }
129
+ expiration_date = datetime.now() + timedelta(days=days_map[expires])
130
+
131
+ # Save token (in a real app, you'd save to database)
132
+ self.save_token_to_file('api_token', token, token_name, expiration_date)
133
+
134
+ self.stdout.write(self.style.SUCCESS(f'✅ API Token created: {token}'))
135
+ self.stdout.write(f'📝 Name: {token_name}')
136
+ if expiration_date:
137
+ self.stdout.write(f'⏰ Expires: {expiration_date.strftime("%Y-%m-%d %H:%M:%S")}')
138
+
139
+ def create_auth_token(self):
140
+ """Create authentication token"""
141
+ self.stdout.write(self.style.SUCCESS('🔐 Creating Auth Token...'))
142
+
143
+ # Get token details
144
+ token_name = questionary.text('Token name:').ask()
145
+ if not token_name:
146
+ self.stdout.write(self.style.ERROR('❌ Token name is required'))
147
+ return
148
+
149
+ token_length = questionary.select(
150
+ 'Token length:',
151
+ choices=['32', '64', '128']
152
+ ).ask()
153
+
154
+ # Generate token
155
+ token = self.generate_token(int(token_length))
156
+
157
+ # Save token
158
+ self.save_token_to_file('auth_token', token, token_name)
159
+
160
+ self.stdout.write(self.style.SUCCESS(f'✅ Auth Token created: {token}'))
161
+ self.stdout.write(f'📝 Name: {token_name}')
162
+
163
+ def create_secret_key(self):
164
+ """Create secret key"""
165
+ self.stdout.write(self.style.SUCCESS('🔒 Creating Secret Key...'))
166
+
167
+ # Get key details
168
+ key_name = questionary.text('Secret key name:').ask()
169
+ if not key_name:
170
+ self.stdout.write(self.style.ERROR('❌ Key name is required'))
171
+ return
172
+
173
+ key_length = questionary.select(
174
+ 'Key length:',
175
+ choices=['32', '64', '128', '256']
176
+ ).ask()
177
+
178
+ # Generate secret key
179
+ secret_key = self.generate_secret_key(int(key_length))
180
+
181
+ # Save key
182
+ self.save_token_to_file('secret_key', secret_key, key_name)
183
+
184
+ self.stdout.write(self.style.SUCCESS(f'✅ Secret Key created: {secret_key}'))
185
+ self.stdout.write(f'📝 Name: {key_name}')
186
+
187
+ def create_token_for_user_interactive(self):
188
+ """Create token for user interactively"""
189
+ self.stdout.write(self.style.SUCCESS('👤 Creating Token for User...'))
190
+
191
+ # Get user
192
+ username = questionary.text('Username:').ask()
193
+ if not username:
194
+ self.stdout.write(self.style.ERROR('❌ Username is required'))
195
+ return
196
+
197
+ # Check if user exists
198
+ try:
199
+ user = User.objects.get(username=username)
200
+ except User.DoesNotExist:
201
+ self.stdout.write(self.style.ERROR(f'❌ User {username} does not exist'))
202
+ return
203
+
204
+ # Get token type
205
+ token_type = questionary.select(
206
+ 'Token type:',
207
+ choices=['API Token', 'Auth Token', 'Secret Key']
208
+ ).ask()
209
+
210
+ # Get token length
211
+ token_length = questionary.select(
212
+ 'Token length:',
213
+ choices=['32', '64', '128']
214
+ ).ask()
215
+
216
+ # Get expiration
217
+ expires = questionary.select(
218
+ 'Token expiration:',
219
+ choices=['Never', '30 days', '90 days', '1 year']
220
+ ).ask()
221
+
222
+ # Create token
223
+ self.create_token_for_user(
224
+ username=username,
225
+ token_type=token_type.lower().replace(' ', '_'),
226
+ length=int(token_length),
227
+ expires_days=None if expires == 'Never' else {
228
+ '30 days': 30,
229
+ '90 days': 90,
230
+ '1 year': 365
231
+ }[expires]
232
+ )
233
+
234
+ def create_token_for_user(self, username, token_type, length=32, expires_days=None):
235
+ """Create token for specific user"""
236
+ try:
237
+ user = User.objects.get(username=username)
238
+ except User.DoesNotExist:
239
+ self.stdout.write(self.style.ERROR(f'❌ User {username} does not exist'))
240
+ return
241
+
242
+ # Generate token
243
+ if token_type == 'secret_key':
244
+ token = self.generate_secret_key(length)
245
+ else:
246
+ token = self.generate_token(length)
247
+
248
+ # Calculate expiration
249
+ expiration_date = None
250
+ if expires_days:
251
+ expiration_date = datetime.now() + timedelta(days=expires_days)
252
+
253
+ # Save token
254
+ token_name = f"{username}_{token_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
255
+ self.save_token_to_file(token_type, token, token_name, expiration_date, user)
256
+
257
+ self.stdout.write(self.style.SUCCESS(f'✅ {token_type.title()} created for {username}'))
258
+ self.stdout.write(f'🔑 Token: {token}')
259
+ self.stdout.write(f'📝 Name: {token_name}')
260
+ if expiration_date:
261
+ self.stdout.write(f'⏰ Expires: {expiration_date.strftime("%Y-%m-%d %H:%M:%S")}')
262
+
263
+ def generate_django_secret_key(self):
264
+ """Generate Django secret key"""
265
+ self.stdout.write(self.style.SUCCESS('🔐 Generating Django Secret Key...'))
266
+
267
+ # Generate Django-compatible secret key
268
+ secret_key = self.generate_django_secret()
269
+
270
+ # Save to file
271
+ self.save_token_to_file('django_secret', secret_key, 'django_secret_key')
272
+
273
+ self.stdout.write(self.style.SUCCESS(f'✅ Django Secret Key generated: {secret_key}'))
274
+ self.stdout.write('💡 Add this to your .env file as SECRET_KEY=...')
275
+
276
+ def generate_token(self, length=32):
277
+ """Generate random token"""
278
+ alphabet = string.ascii_letters + string.digits
279
+ return ''.join(secrets.choice(alphabet) for _ in range(length))
280
+
281
+ def generate_secret_key(self, length=64):
282
+ """Generate secret key"""
283
+ alphabet = string.ascii_letters + string.digits + string.punctuation
284
+ # Remove characters that might cause issues in config files
285
+ alphabet = alphabet.replace('"', '').replace("'", '').replace('\\', '')
286
+ return ''.join(secrets.choice(alphabet) for _ in range(length))
287
+
288
+ def generate_django_secret(self):
289
+ """Generate Django-compatible secret key"""
290
+ return ''.join(secrets.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(50))
291
+
292
+ def save_token_to_file(self, token_type, token, name, expiration_date=None, user=None):
293
+ """Save token to file"""
294
+ # Create tokens directory
295
+ tokens_dir = Path('tokens')
296
+ tokens_dir.mkdir(exist_ok=True)
297
+
298
+ # Create token file
299
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
300
+ filename = f"{token_type}_{name}_{timestamp}.txt"
301
+ filepath = tokens_dir / filename
302
+
303
+ with open(filepath, 'w') as f:
304
+ f.write(f"Token Type: {token_type}\n")
305
+ f.write(f"Name: {name}\n")
306
+ f.write(f"Token: {token}\n")
307
+ f.write(f"Created: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
308
+ if expiration_date:
309
+ f.write(f"Expires: {expiration_date.strftime('%Y-%m-%d %H:%M:%S')}\n")
310
+ if user:
311
+ f.write(f"User: {user.username} ({user.email})\n")
312
+ f.write(f"\n# Add to your configuration:\n")
313
+ f.write(f"# {token_type.upper()}_KEY={token}\n")
314
+
315
+ self.stdout.write(f'💾 Token saved to: {filepath}')
316
+
317
+ # Also save to .env format
318
+ env_filename = f"{token_type}_{name}_{timestamp}.env"
319
+ env_filepath = tokens_dir / env_filename
320
+
321
+ with open(env_filepath, 'w') as f:
322
+ f.write(f"# {token_type.title()} - {name}\n")
323
+ f.write(f"# Created: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
324
+ if expiration_date:
325
+ f.write(f"# Expires: {expiration_date.strftime('%Y-%m-%d %H:%M:%S')}\n")
326
+ f.write(f"{token_type.upper()}_KEY={token}\n")
327
+
328
+ self.stdout.write(f'💾 Environment file saved to: {env_filepath}')
@@ -0,0 +1,313 @@
1
+ """
2
+ Show URLs Command
3
+
4
+ Display all available URLs in the Django project with Rich formatting.
5
+ Useful for development and webhook configuration.
6
+ """
7
+
8
+ import re
9
+ from django.core.management.base import BaseCommand
10
+ from django.urls import get_resolver
11
+ from django.conf import settings
12
+ from django_cfg.modules.django_logging import get_logger
13
+
14
+
15
+ # Rich imports for beautiful output
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+ from rich.text import Text
19
+ from rich.table import Table
20
+ from rich.align import Align
21
+
22
+ from django_cfg.core.state import get_current_config
23
+
24
+
25
+ logger = get_logger('list_urls')
26
+
27
+ class Command(BaseCommand):
28
+ """Command to display all available URLs in the project."""
29
+
30
+ # Web execution metadata
31
+ web_executable = True
32
+ requires_input = False
33
+ is_destructive = False
34
+
35
+ help = "Display all available URLs with Rich formatting"
36
+
37
+ def __init__(self, *args, **kwargs):
38
+ super().__init__(*args, **kwargs)
39
+ self.console = Console()
40
+ self.config = None
41
+
42
+ def add_arguments(self, parser):
43
+ parser.add_argument(
44
+ "--filter",
45
+ type=str,
46
+ help="Filter URLs containing this string",
47
+ default=None
48
+ )
49
+ parser.add_argument(
50
+ "--webhook",
51
+ action="store_true",
52
+ help="Show only webhook-related URLs"
53
+ )
54
+ parser.add_argument(
55
+ "--api",
56
+ action="store_true",
57
+ help="Show only API URLs"
58
+ )
59
+ parser.add_argument(
60
+ "--with-ngrok",
61
+ action="store_true",
62
+ help="Show ngrok URLs alongside local URLs"
63
+ )
64
+
65
+ def handle(self, *args, **options):
66
+ logger.info("Starting list_urls command")
67
+ filter_str = options["filter"]
68
+ webhook_only = options["webhook"]
69
+ api_only = options["api"]
70
+ with_ngrok = options["with_ngrok"]
71
+
72
+ # Show header
73
+ self.show_header()
74
+
75
+ # Load config
76
+ self.load_config()
77
+
78
+ # Get all URLs
79
+ urls = self.get_all_urls()
80
+
81
+ # Filter URLs
82
+ if filter_str:
83
+ urls = [url for url in urls if filter_str.lower() in url['pattern'].lower()]
84
+
85
+ if webhook_only:
86
+ urls = [url for url in urls if 'webhook' in url['pattern'].lower() or 'hook' in url['pattern'].lower()]
87
+
88
+ if api_only:
89
+ urls = [url for url in urls if '/api/' in url['pattern'] or url['pattern'].startswith('api/')]
90
+
91
+ # Display URLs
92
+ self.display_urls(urls, with_ngrok)
93
+
94
+ # Show webhook info if requested
95
+ if webhook_only or with_ngrok:
96
+ self.show_webhook_info()
97
+
98
+ def show_header(self):
99
+ """Show beautiful header with Rich."""
100
+ title = Text("Django URLs Overview", style="bold cyan")
101
+ subtitle = Text("All available URLs in your project", style="dim")
102
+
103
+ header_content = Align.center(
104
+ Text.assemble(
105
+ title, "\n",
106
+ subtitle
107
+ )
108
+ )
109
+
110
+ self.console.print()
111
+ self.console.print(Panel(
112
+ header_content,
113
+ title="🔗 URL Inspector",
114
+ border_style="bright_blue",
115
+ padding=(1, 2)
116
+ ))
117
+
118
+ def load_config(self):
119
+ """Load Django CFG configuration."""
120
+ try:
121
+ self.config = get_current_config()
122
+ except Exception as e:
123
+ self.console.print(f"[yellow]⚠️ Failed to load config: {e}[/yellow]")
124
+ self.config = None
125
+
126
+ def get_all_urls(self):
127
+ """Extract all URLs from Django URL configuration."""
128
+ urls = []
129
+ resolver = get_resolver()
130
+
131
+ def extract_urls(url_patterns, prefix=''):
132
+ for pattern in url_patterns:
133
+ if hasattr(pattern, 'url_patterns'):
134
+ # This is an include() - recurse
135
+ new_prefix = prefix + str(pattern.pattern)
136
+ extract_urls(pattern.url_patterns, new_prefix)
137
+ else:
138
+ # This is a regular URL pattern
139
+ full_pattern = prefix + str(pattern.pattern)
140
+
141
+ # Clean up the pattern
142
+ clean_pattern = re.sub(r'\^|\$', '', full_pattern)
143
+ clean_pattern = re.sub(r'\\/', '/', clean_pattern)
144
+
145
+ # Get view info
146
+ view_name = getattr(pattern, 'name', None)
147
+ view_func = getattr(pattern, 'callback', None)
148
+
149
+ if view_func:
150
+ if hasattr(view_func, 'view_class'):
151
+ # Class-based view
152
+ view_info = f"{view_func.view_class.__name__}"
153
+ module = view_func.view_class.__module__
154
+ elif hasattr(view_func, '__name__'):
155
+ # Function-based view
156
+ view_info = f"{view_func.__name__}()"
157
+ module = getattr(view_func, '__module__', 'unknown')
158
+ else:
159
+ view_info = str(view_func)
160
+ module = 'unknown'
161
+ else:
162
+ view_info = 'Unknown'
163
+ module = 'unknown'
164
+
165
+ urls.append({
166
+ 'pattern': clean_pattern,
167
+ 'name': view_name,
168
+ 'view': view_info,
169
+ 'module': module
170
+ })
171
+
172
+ extract_urls(resolver.url_patterns)
173
+ return urls
174
+
175
+ def display_urls(self, urls, with_ngrok=False):
176
+ """Display URLs in a Rich table."""
177
+ if not urls:
178
+ self.console.print("[yellow]No URLs found matching the criteria.[/yellow]")
179
+ return
180
+
181
+ # Create table
182
+ table = Table(title=f"🔗 Found {len(urls)} URLs", show_header=True, header_style="bold cyan")
183
+ table.add_column("URL Pattern", style="cyan", width=40)
184
+ table.add_column("Name", style="white", width=20)
185
+ table.add_column("View", style="green", width=25)
186
+
187
+ if with_ngrok:
188
+ table.add_column("Ngrok URL", style="magenta", width=40)
189
+
190
+ # Get base URLs
191
+ base_url = self.get_base_url()
192
+ ngrok_url = self.get_ngrok_url() if with_ngrok else None
193
+
194
+ # Add rows
195
+ for url in urls[:50]: # Limit to first 50 URLs
196
+ pattern = url['pattern']
197
+ name = url['name'] or '—'
198
+ view = url['view']
199
+
200
+ # Truncate long view names
201
+ if len(view) > 23:
202
+ view = view[:20] + "..."
203
+
204
+ row = [pattern, name, view]
205
+
206
+ if with_ngrok:
207
+ if ngrok_url:
208
+ full_ngrok_url = f"{ngrok_url.rstrip('/')}/{pattern.lstrip('/')}"
209
+ row.append(full_ngrok_url)
210
+ else:
211
+ row.append("—")
212
+
213
+ table.add_row(*row)
214
+
215
+ if len(urls) > 50:
216
+ table.caption = f"Showing first 50 of {len(urls)} URLs"
217
+
218
+ self.console.print(table)
219
+
220
+ # Show base URL info
221
+ info_table = Table(show_header=False, box=None)
222
+ info_table.add_column("Info", style="cyan")
223
+ info_table.add_column("Value", style="white")
224
+
225
+ info_table.add_row("🌐 Base URL:", base_url)
226
+ if with_ngrok and ngrok_url:
227
+ info_table.add_row("🔗 Ngrok URL:", ngrok_url)
228
+
229
+ self.console.print()
230
+ self.console.print(info_table)
231
+
232
+ def show_webhook_info(self):
233
+ """Show webhook-specific information using reverse."""
234
+ self.console.print()
235
+
236
+ webhook_table = Table(title="🔔 Webhook Configuration", show_header=True, header_style="bold yellow")
237
+ webhook_table.add_column("Service", style="white", width=25)
238
+ webhook_table.add_column("Local URL", style="cyan", width=60)
239
+ webhook_table.add_column("Ngrok URL", style="magenta", width=60)
240
+
241
+ base_url = self.get_base_url()
242
+ ngrok_url = self.get_ngrok_url()
243
+
244
+ # Get webhook URLs using reverse
245
+ try:
246
+ from django.urls import reverse
247
+
248
+ # Common webhook endpoints with their URL names
249
+ webhooks = [
250
+ ("Twilio Message Status", "cfg_accounts:webhook-message-status"),
251
+ ("Twilio Verification", "cfg_accounts:webhook-verification-status"),
252
+ ]
253
+
254
+ for service, url_name in webhooks:
255
+ try:
256
+ # Get the reversed URL path
257
+ url_path = reverse(url_name)
258
+
259
+ # Build full URLs
260
+ local_full = f"{base_url.rstrip('/')}{url_path}"
261
+ ngrok_full = f"{ngrok_url.rstrip('/')}{url_path}" if ngrok_url else "—"
262
+
263
+ webhook_table.add_row(service, local_full, ngrok_full)
264
+
265
+ except Exception as e:
266
+ # Fallback if reverse fails
267
+ self.console.print(f"[yellow]⚠️ Could not reverse URL for {service}: {e}[/yellow]")
268
+ fallback_path = f"/api/accounts/webhook/{service.lower().replace(' ', '-').replace('twilio ', '')}/"
269
+ local_full = f"{base_url.rstrip('/')}{fallback_path}"
270
+ ngrok_full = f"{ngrok_url.rstrip('/')}{fallback_path}" if ngrok_url else "—"
271
+ webhook_table.add_row(service, local_full, ngrok_full)
272
+
273
+ except ImportError:
274
+ # Fallback if Django is not available
275
+ webhooks = [
276
+ ("Twilio Message Status", "/api/accounts/webhook/message-status/"),
277
+ ("Twilio Verification", "/api/accounts/webhook/verification-status/"),
278
+ ]
279
+
280
+ for service, endpoint in webhooks:
281
+ local_full = f"{base_url.rstrip('/')}{endpoint}"
282
+ ngrok_full = f"{ngrok_url.rstrip('/')}{endpoint}" if ngrok_url else "—"
283
+ webhook_table.add_row(service, local_full, ngrok_full)
284
+
285
+ self.console.print(webhook_table)
286
+
287
+ # Show tips
288
+ tips = [
289
+ "💡 Use ngrok URLs for webhook configuration in production services",
290
+ "🔒 Always validate webhook signatures in production",
291
+ "📝 Test webhooks using the test_twilio management command",
292
+ ]
293
+
294
+ for tip in tips:
295
+ self.console.print(f"[dim]{tip}[/dim]")
296
+
297
+ def get_base_url(self):
298
+ """Get base URL for the application."""
299
+ if self.config:
300
+ return self.config.api_url
301
+ else:
302
+ # Fallback to Django settings
303
+ debug = getattr(settings, 'DEBUG', True)
304
+ if debug:
305
+ return "http://localhost:8000"
306
+ else:
307
+ return "https://yourdomain.com"
308
+
309
+ def get_ngrok_url(self):
310
+ """Get ngrok URL if available."""
311
+ if self.config:
312
+ return self.config.get_ngrok_url()
313
+ return None