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,302 @@
1
+ """
2
+ Superuser Command for Django Config Toolkit
3
+ Enhanced superuser creation with validation and configuration.
4
+ """
5
+
6
+ import os
7
+ import getpass
8
+ from pathlib import Path
9
+ from django.core.management.base import BaseCommand
10
+ from django.core.management import call_command
11
+ from django.contrib.auth import get_user_model
12
+ from django.core.validators import validate_email
13
+ from django.core.exceptions import ValidationError
14
+ from django.conf import settings
15
+ import questionary
16
+ from datetime import datetime
17
+ from django_cfg.modules.django_logging import get_logger
18
+
19
+ User = get_user_model()
20
+
21
+
22
+ logger = get_logger('superuser')
23
+
24
+ class Command(BaseCommand):
25
+ # Web execution metadata
26
+ web_executable = False
27
+ requires_input = True
28
+ is_destructive = False
29
+
30
+ help = 'Create a superuser with enhanced validation and configuration'
31
+
32
+ def add_arguments(self, parser):
33
+ parser.add_argument(
34
+ '--username',
35
+ type=str,
36
+ help='Username for superuser'
37
+ )
38
+ parser.add_argument(
39
+ '--email',
40
+ type=str,
41
+ help='Email for superuser'
42
+ )
43
+ parser.add_argument(
44
+ '--password',
45
+ type=str,
46
+ help='Password for superuser'
47
+ )
48
+ parser.add_argument(
49
+ '--first-name',
50
+ type=str,
51
+ help='First name for superuser'
52
+ )
53
+ parser.add_argument(
54
+ '--last-name',
55
+ type=str,
56
+ help='Last name for superuser'
57
+ )
58
+ parser.add_argument(
59
+ '--interactive',
60
+ action='store_true',
61
+ help='Run in interactive mode'
62
+ )
63
+
64
+ def handle(self, *args, **options):
65
+ logger.info("Starting superuser command")
66
+ if options['interactive'] or not any([options['username'], options['email'], options['password']]):
67
+ self.create_superuser_interactive()
68
+ else:
69
+ self.create_superuser_non_interactive(options)
70
+
71
+ def create_superuser_interactive(self):
72
+ """Create superuser interactively"""
73
+ self.stdout.write(self.style.SUCCESS('\n👑 Superuser Creation Tool - Django Config Toolkit\n'))
74
+
75
+ # Check if superuser already exists
76
+ if User.objects.filter(is_superuser=True).exists():
77
+ self.stdout.write(self.style.WARNING('⚠️ Superuser already exists'))
78
+ overwrite = questionary.confirm('Do you want to create another superuser?').ask()
79
+ if not overwrite:
80
+ self.stdout.write('Goodbye! 👋')
81
+ return
82
+
83
+ # Get user details
84
+ user_data = self.get_user_details_interactive()
85
+
86
+ # Validate data
87
+ if not self.validate_user_data(user_data):
88
+ return
89
+
90
+ # Create superuser
91
+ self.create_superuser(user_data)
92
+
93
+ def create_superuser_non_interactive(self, options):
94
+ """Create superuser non-interactively"""
95
+ user_data = {
96
+ 'username': options['username'],
97
+ 'email': options['email'],
98
+ 'password': options['password'],
99
+ 'first_name': options['first_name'] or '',
100
+ 'last_name': options['last_name'] or '',
101
+ }
102
+
103
+ # Validate required fields
104
+ if not all([user_data['username'], user_data['email'], user_data['password']]):
105
+ self.stdout.write(self.style.ERROR('❌ Username, email, and password are required'))
106
+ return
107
+
108
+ # Validate data
109
+ if not self.validate_user_data(user_data):
110
+ return
111
+
112
+ # Create superuser
113
+ self.create_superuser(user_data)
114
+
115
+ def get_user_details_interactive(self):
116
+ """Get user details interactively"""
117
+ user_data = {}
118
+
119
+ # Username
120
+ while True:
121
+ username = questionary.text('Username:').ask()
122
+ if not username:
123
+ self.stdout.write(self.style.ERROR('❌ Username is required'))
124
+ continue
125
+
126
+ if User.objects.filter(username=username).exists():
127
+ self.stdout.write(self.style.ERROR('❌ Username already exists'))
128
+ continue
129
+
130
+ user_data['username'] = username
131
+ break
132
+
133
+ # Email
134
+ while True:
135
+ email = questionary.text('Email:').ask()
136
+ if not email:
137
+ self.stdout.write(self.style.ERROR('❌ Email is required'))
138
+ continue
139
+
140
+ try:
141
+ validate_email(email)
142
+ except ValidationError:
143
+ self.stdout.write(self.style.ERROR('❌ Invalid email format'))
144
+ continue
145
+
146
+ if User.objects.filter(email=email).exists():
147
+ self.stdout.write(self.style.ERROR('❌ Email already exists'))
148
+ continue
149
+
150
+ user_data['email'] = email
151
+ break
152
+
153
+ # Password
154
+ while True:
155
+ password = questionary.password('Password:').ask()
156
+ if not password:
157
+ self.stdout.write(self.style.ERROR('❌ Password is required'))
158
+ continue
159
+
160
+ if len(password) < 8:
161
+ self.stdout.write(self.style.WARNING('⚠️ Password should be at least 8 characters'))
162
+ confirm = questionary.confirm('Continue with weak password?').ask()
163
+ if not confirm:
164
+ continue
165
+
166
+ confirm_password = questionary.password('Confirm password:').ask()
167
+ if password != confirm_password:
168
+ self.stdout.write(self.style.ERROR('❌ Passwords do not match'))
169
+ continue
170
+
171
+ user_data['password'] = password
172
+ break
173
+
174
+ # Optional fields
175
+ user_data['first_name'] = questionary.text('First name (optional):').ask() or ''
176
+ user_data['last_name'] = questionary.text('Last name (optional):').ask() or ''
177
+
178
+ return user_data
179
+
180
+ def validate_user_data(self, user_data):
181
+ """Validate user data"""
182
+ errors = []
183
+
184
+ # Check required fields
185
+ if not user_data.get('username'):
186
+ errors.append('Username is required')
187
+
188
+ if not user_data.get('email'):
189
+ errors.append('Email is required')
190
+
191
+ if not user_data.get('password'):
192
+ errors.append('Password is required')
193
+
194
+ # Validate email format
195
+ if user_data.get('email'):
196
+ try:
197
+ validate_email(user_data['email'])
198
+ except ValidationError:
199
+ errors.append('Invalid email format')
200
+
201
+ # Check if user already exists
202
+ if user_data.get('username') and User.objects.filter(username=user_data['username']).exists():
203
+ errors.append('Username already exists')
204
+
205
+ if user_data.get('email') and User.objects.filter(email=user_data['email']).exists():
206
+ errors.append('Email already exists')
207
+
208
+ # Password strength
209
+ if user_data.get('password') and len(user_data['password']) < 8:
210
+ errors.append('Password should be at least 8 characters')
211
+
212
+ if errors:
213
+ self.stdout.write(self.style.ERROR('❌ Validation errors:'))
214
+ for error in errors:
215
+ self.stdout.write(f' - {error}')
216
+ return False
217
+
218
+ return True
219
+
220
+ def create_superuser(self, user_data):
221
+ """Create the superuser"""
222
+ try:
223
+ # Create user
224
+ user = User.objects.create_user(
225
+ username=user_data['username'],
226
+ email=user_data['email'],
227
+ password=user_data['password'],
228
+ first_name=user_data['first_name'],
229
+ last_name=user_data['last_name'],
230
+ is_staff=True,
231
+ is_superuser=True,
232
+ is_active=True
233
+ )
234
+
235
+ # Save user details to file
236
+ self.save_superuser_details(user, user_data['password'])
237
+
238
+ self.stdout.write(self.style.SUCCESS(f'✅ Superuser created successfully!'))
239
+ self.stdout.write(f'👤 Username: {user.username}')
240
+ self.stdout.write(f'📧 Email: {user.email}')
241
+ self.stdout.write(f'👑 Is Superuser: {user.is_superuser}')
242
+ self.stdout.write(f'🔧 Is Staff: {user.is_staff}')
243
+ self.stdout.write(f'✅ Is Active: {user.is_active}')
244
+
245
+ # Show next steps
246
+ self.show_next_steps()
247
+
248
+ except Exception as e:
249
+ self.stdout.write(self.style.ERROR(f'❌ Error creating superuser: {e}'))
250
+
251
+ def save_superuser_details(self, user, password):
252
+ """Save superuser details to file"""
253
+ # Create superusers directory
254
+ superusers_dir = Path('superusers')
255
+ superusers_dir.mkdir(exist_ok=True)
256
+
257
+ # Create details file
258
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
259
+ filename = f"superuser_{user.username}_{timestamp}.txt"
260
+ filepath = superusers_dir / filename
261
+
262
+ with open(filepath, 'w') as f:
263
+ f.write(f"Superuser Details\n")
264
+ f.write(f"================\n\n")
265
+ f.write(f"Username: {user.username}\n")
266
+ f.write(f"Email: {user.email}\n")
267
+ f.write(f"First Name: {user.first_name}\n")
268
+ f.write(f"Is Superuser: {user.is_superuser}\n")
269
+ f.write(f"Is Staff: {user.is_staff}\n")
270
+ f.write(f"Is Active: {user.is_active}\n")
271
+ f.write(f"Date Created: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
272
+ f.write(f"\n⚠️ IMPORTANT: Store this password securely!\n")
273
+ f.write(f"Password: {password}\n")
274
+
275
+ self.stdout.write(f'💾 Superuser details saved to: {filepath}')
276
+
277
+ def show_next_steps(self):
278
+ """Show next steps after creating superuser"""
279
+ self.stdout.write(self.style.SUCCESS('\n🎉 Next Steps:'))
280
+ self.stdout.write('1. 🚀 Start your Django server: python manage.py runserver')
281
+ self.stdout.write('2. 🌐 Visit Django admin: http://localhost:8000/admin/')
282
+ self.stdout.write('3. 🔑 Login with your superuser credentials')
283
+ self.stdout.write('4. ⚙️ Configure your application settings')
284
+ self.stdout.write('5. 📚 Read the Django Config Toolkit documentation')
285
+
286
+ # Show health check
287
+ self.stdout.write('\n🔍 Health Check:')
288
+ try:
289
+ call_command('check', verbosity=0)
290
+ self.stdout.write('✅ Django configuration is valid')
291
+ except Exception as e:
292
+ self.stdout.write(f'⚠️ Django configuration issues: {e}')
293
+
294
+ def check_existing_superusers(self):
295
+ """Check existing superusers"""
296
+ superusers = User.objects.filter(is_superuser=True)
297
+ if superusers.exists():
298
+ self.stdout.write(self.style.WARNING(f'⚠️ Found {superusers.count()} existing superuser(s):'))
299
+ for user in superusers:
300
+ self.stdout.write(f' - {user.username} ({user.email})')
301
+ return True
302
+ return False
@@ -0,0 +1,390 @@
1
+ """
2
+ 🌳 Django CFG Tree Command
3
+
4
+ Display Django project structure in a tree format based on configuration.
5
+ """
6
+
7
+ import os
8
+ import subprocess
9
+ from pathlib import Path
10
+ from typing import List, Optional
11
+
12
+ from django.core.management.base import BaseCommand, CommandError
13
+ from django.conf import settings
14
+ from django_cfg.modules.django_logging import get_logger
15
+
16
+
17
+ from django_cfg.core.state import get_current_config
18
+ from django_cfg.utils.path_resolution import PathResolver
19
+
20
+
21
+ logger = get_logger('tree')
22
+
23
+ class Command(BaseCommand):
24
+ """Display Django project structure in tree format."""
25
+
26
+ # Web execution metadata
27
+ web_executable = True
28
+ requires_input = False
29
+ is_destructive = False
30
+
31
+ help = "Display Django project structure based on django-cfg configuration"
32
+
33
+ def add_arguments(self, parser):
34
+ """Add command arguments."""
35
+ parser.add_argument(
36
+ '--depth', '-L',
37
+ type=int,
38
+ default=5,
39
+ help='Maximum depth to display (default: 5)'
40
+ )
41
+ parser.add_argument(
42
+ '--all', '-a',
43
+ action='store_true',
44
+ help='Show all files including hidden ones'
45
+ )
46
+ parser.add_argument(
47
+ '--dirs-only', '-d',
48
+ action='store_true',
49
+ help='Show directories only'
50
+ )
51
+ parser.add_argument(
52
+ '--no-ignore', '-n',
53
+ action='store_true',
54
+ help='Do not ignore common directories (node_modules, .git, etc.)'
55
+ )
56
+ parser.add_argument(
57
+ '--custom-ignore',
58
+ type=str,
59
+ help='Custom ignore pattern (pipe-separated, e.g., "*.pyc|temp|logs")'
60
+ )
61
+ parser.add_argument(
62
+ '--include-docs',
63
+ action='store_true',
64
+ help='Legacy option - @docs directories are now included by default'
65
+ )
66
+ parser.add_argument(
67
+ '--include-docker',
68
+ action='store_true',
69
+ help='Include Docker configuration and volumes'
70
+ )
71
+ parser.add_argument(
72
+ '--include-logs',
73
+ action='store_true',
74
+ help='Include log files and directories'
75
+ )
76
+ parser.add_argument(
77
+ '--output', '-o',
78
+ type=str,
79
+ help='Output to file instead of stdout'
80
+ )
81
+ parser.add_argument(
82
+ '--format',
83
+ choices=['tree', 'json', 'xml'],
84
+ default='tree',
85
+ help='Output format (default: tree)'
86
+ )
87
+
88
+ def handle(self, *args, **options):
89
+ """Execute the command."""
90
+ logger.info("Starting tree command")
91
+ try:
92
+ # Get django-cfg configuration
93
+ config = get_current_config()
94
+
95
+ # Determine base directory
96
+ base_dir = self.get_base_directory(config)
97
+
98
+ self.stdout.write(
99
+ self.style.SUCCESS(f"📁 Django Project Structure: {base_dir}")
100
+ )
101
+
102
+ # Try to get environment info
103
+ try:
104
+ env_info = getattr(config, 'env_mode', 'unknown')
105
+ self.stdout.write(
106
+ self.style.HTTP_INFO(f"🔧 Environment: {env_info}")
107
+ )
108
+ except Exception:
109
+ pass
110
+
111
+ self.stdout.write("")
112
+
113
+ # Check if tree command is available
114
+ if not self.is_tree_available():
115
+ self.stdout.write(
116
+ self.style.WARNING("⚠️ 'tree' command not found. Using fallback implementation.")
117
+ )
118
+ self.display_fallback_tree(base_dir, options)
119
+ else:
120
+ self.display_tree(base_dir, options)
121
+
122
+ except Exception as e:
123
+ raise CommandError(f"Failed to display project tree: {e}")
124
+
125
+ def get_base_directory(self, config) -> Path:
126
+ """Get the base directory for the Django project tree."""
127
+ try:
128
+ # First try to use PathResolver to find the actual Django project root
129
+ return PathResolver.find_project_root()
130
+ except Exception:
131
+ try:
132
+ # Try to get from config
133
+ if hasattr(config, 'base_dir') and config.base_dir:
134
+ return Path(config.base_dir)
135
+
136
+ # Fallback to Django BASE_DIR
137
+ if hasattr(settings, 'BASE_DIR'):
138
+ return Path(settings.BASE_DIR)
139
+
140
+ # Last resort: current working directory
141
+ return Path.cwd()
142
+
143
+ except Exception as e:
144
+ self.stdout.write(
145
+ self.style.WARNING(f"Could not determine base directory: {e}")
146
+ )
147
+ return Path.cwd()
148
+
149
+ def is_tree_available(self) -> bool:
150
+ """Check if tree command is available."""
151
+ try:
152
+ subprocess.run(['tree', '--version'],
153
+ capture_output=True,
154
+ check=True)
155
+ return True
156
+ except (subprocess.CalledProcessError, FileNotFoundError):
157
+ return False
158
+
159
+ def build_ignore_pattern(self, options) -> str:
160
+ """Build ignore pattern for tree command."""
161
+ if options['no_ignore']:
162
+ return ""
163
+
164
+ # Default ignore patterns
165
+ default_ignores = [
166
+ 'node_modules',
167
+ '.git',
168
+ '.venv',
169
+ 'venv',
170
+ '.env',
171
+ '.DS_Store',
172
+ '__pycache__',
173
+ '*.pyc',
174
+ '*.pyo',
175
+ '*.pyd',
176
+ '.pytest_cache',
177
+ '.coverage',
178
+ 'htmlcov',
179
+ 'coverage',
180
+ '.tox',
181
+ 'dist',
182
+ 'build',
183
+ '*.egg-info',
184
+ 'staticfiles',
185
+ 'media',
186
+ '@old*',
187
+ 'parsers',
188
+ 'openapi',
189
+ 'modules',
190
+ 'django_cfg',
191
+ # Package manager files
192
+ 'package-lock.json',
193
+ 'pnpm-lock.yaml',
194
+ 'poetry.lock',
195
+ 'yarn.lock',
196
+ # Empty/system directories
197
+ 'db',
198
+ 'static'
199
+ ]
200
+
201
+ # Conditionally add patterns based on options
202
+ # Note: @docs folders are now included by default to show documentation
203
+
204
+ if not options.get('include_docker'):
205
+ default_ignores.extend([
206
+ 'docker',
207
+ 'devops'
208
+ ])
209
+
210
+ if not options.get('include_logs'):
211
+ default_ignores.extend([
212
+ 'logs',
213
+ '*.log',
214
+ 'log'
215
+ ])
216
+
217
+ # Add custom ignores
218
+ if options['custom_ignore']:
219
+ custom_ignores = options['custom_ignore'].split('|')
220
+ default_ignores.extend(custom_ignores)
221
+
222
+ return '|'.join(default_ignores)
223
+
224
+ def display_tree(self, base_dir: Path, options):
225
+ """Display tree using system tree command."""
226
+ try:
227
+ # Build tree command
228
+ cmd = ['tree']
229
+
230
+ # Add ignore pattern
231
+ ignore_pattern = self.build_ignore_pattern(options)
232
+ if ignore_pattern:
233
+ cmd.extend(['-I', ignore_pattern])
234
+
235
+ # Add options
236
+ cmd.append('--dirsfirst') # Directories first
237
+
238
+ if options['depth']:
239
+ cmd.extend(['-L', str(options['depth'])])
240
+
241
+ if options['dirs_only']:
242
+ cmd.append('-d')
243
+
244
+ if options['all']:
245
+ cmd.append('-a')
246
+
247
+ # Add format options
248
+ if options['format'] == 'json':
249
+ cmd.append('-J')
250
+ elif options['format'] == 'xml':
251
+ cmd.append('-X')
252
+
253
+ # Add base directory
254
+ cmd.append(str(base_dir))
255
+
256
+ # Execute command
257
+ if options['output']:
258
+ with open(options['output'], 'w') as f:
259
+ result = subprocess.run(cmd, stdout=f, stderr=subprocess.PIPE, text=True)
260
+ if result.returncode == 0:
261
+ self.stdout.write(
262
+ self.style.SUCCESS(f"✅ Tree output saved to: {options['output']}")
263
+ )
264
+ else:
265
+ self.stdout.write(
266
+ self.style.ERROR(f"❌ Tree command failed: {result.stderr}")
267
+ )
268
+ else:
269
+ result = subprocess.run(cmd, text=True)
270
+ if result.returncode != 0:
271
+ self.stdout.write(
272
+ self.style.ERROR("❌ Tree command failed")
273
+ )
274
+
275
+ except Exception as e:
276
+ self.stdout.write(
277
+ self.style.ERROR(f"❌ Failed to execute tree command: {e}")
278
+ )
279
+ self.display_fallback_tree(base_dir, options)
280
+
281
+ def display_fallback_tree(self, base_dir: Path, options):
282
+ """Fallback tree implementation using Python."""
283
+ self.stdout.write("🐍 Using Python fallback implementation:")
284
+ self.stdout.write("")
285
+
286
+ ignore_patterns = self.build_ignore_pattern(options).split('|') if not options['no_ignore'] else []
287
+
288
+ def should_ignore(path: Path) -> bool:
289
+ """Check if path should be ignored."""
290
+ name = path.name
291
+ for pattern in ignore_patterns:
292
+ if pattern.startswith('*'):
293
+ if name.endswith(pattern[1:]):
294
+ return True
295
+ elif pattern.endswith('*'):
296
+ if name.startswith(pattern[:-1]):
297
+ return True
298
+ elif pattern == name:
299
+ return True
300
+ return False
301
+
302
+ def print_tree(directory: Path, prefix: str = "", depth: int = 0):
303
+ """Recursively print directory tree."""
304
+ if options['depth'] and depth >= options['depth']:
305
+ return
306
+
307
+ try:
308
+ items = []
309
+ for item in directory.iterdir():
310
+ if not options['all'] and item.name.startswith('.'):
311
+ continue
312
+ if should_ignore(item):
313
+ continue
314
+ items.append(item)
315
+
316
+ # Sort: directories first, then files
317
+ items.sort(key=lambda x: (not x.is_dir(), x.name.lower()))
318
+
319
+ for i, item in enumerate(items):
320
+ is_last = i == len(items) - 1
321
+ current_prefix = "└── " if is_last else "├── "
322
+
323
+ if item.is_dir():
324
+ self.stdout.write(f"{prefix}{current_prefix}📁 {item.name}/")
325
+ if not options['dirs_only']:
326
+ next_prefix = prefix + (" " if is_last else "│ ")
327
+ print_tree(item, next_prefix, depth + 1)
328
+ elif not options['dirs_only']:
329
+ # Add file type emoji
330
+ emoji = self.get_file_emoji(item)
331
+ self.stdout.write(f"{prefix}{current_prefix}{emoji} {item.name}")
332
+
333
+ except PermissionError:
334
+ self.stdout.write(f"{prefix}❌ Permission denied")
335
+
336
+ print_tree(base_dir)
337
+
338
+ def get_file_emoji(self, file_path: Path) -> str:
339
+ """Get emoji for file type."""
340
+ suffix = file_path.suffix.lower()
341
+
342
+ emoji_map = {
343
+ '.py': '🐍',
344
+ '.js': '📜',
345
+ '.ts': '📘',
346
+ '.html': '🌐',
347
+ '.css': '🎨',
348
+ '.scss': '🎨',
349
+ '.json': '📋',
350
+ '.yaml': '📄',
351
+ '.yml': '📄',
352
+ '.toml': '⚙️',
353
+ '.ini': '⚙️',
354
+ '.cfg': '⚙️',
355
+ '.conf': '⚙️',
356
+ '.md': '📝',
357
+ '.txt': '📄',
358
+ '.log': '📊',
359
+ '.sql': '🗄️',
360
+ '.db': '🗄️',
361
+ '.sqlite': '🗄️',
362
+ '.sqlite3': '🗄️',
363
+ '.png': '🖼️',
364
+ '.jpg': '🖼️',
365
+ '.jpeg': '🖼️',
366
+ '.gif': '🖼️',
367
+ '.svg': '🖼️',
368
+ '.ico': '🖼️',
369
+ '.pdf': '📕',
370
+ '.zip': '📦',
371
+ '.tar': '📦',
372
+ '.gz': '📦',
373
+ '.requirements': '📋',
374
+ 'requirements.txt': '📋',
375
+ 'Dockerfile': '🐳',
376
+ 'docker-compose.yml': '🐳',
377
+ 'manage.py': '⚙️',
378
+ 'setup.py': '⚙️',
379
+ 'pyproject.toml': '📦',
380
+ 'poetry.lock': '🔒',
381
+ 'Pipfile': '📦',
382
+ 'Pipfile.lock': '🔒',
383
+ }
384
+
385
+ # Check full filename first
386
+ if file_path.name in emoji_map:
387
+ return emoji_map[file_path.name]
388
+
389
+ # Then check extension
390
+ return emoji_map.get(suffix, '📄')