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.
- django_cfg/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/middleware/api_access.py +6 -2
- django_cfg/apps/payments/middleware/rate_limiting.py +2 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +5 -1
- django_cfg/apps/payments/models/managers/api_key_managers.py +0 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +0 -1
- django_cfg/apps/payments/services/core/balance_service.py +5 -5
- django_cfg/apps/payments/services/core/subscription_service.py +1 -2
- django_cfg/apps/payments/views/api/balances.py +8 -7
- django_cfg/apps/payments/views/api/base.py +10 -6
- django_cfg/apps/payments/views/api/currencies.py +53 -10
- django_cfg/apps/payments/views/api/payments.py +3 -1
- django_cfg/apps/payments/views/api/subscriptions.py +2 -5
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +82 -41
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/dashboard/sections/documentation.py +391 -0
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -265
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +238 -0
- django_cfg/models/django/{revolution.py → revolution_legacy.py} +8 -0
- django_cfg/modules/django_admin/management/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +188 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +767 -0
- django_cfg/modules/django_client/core/generator/python.py +751 -0
- django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
- django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
- django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/typescript.py +872 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/spectacular/__init__.py +9 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +16 -5
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/modules/django_unfold/dashboard.py +1 -1
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/RECORD +192 -71
- django_cfg/management/commands/generate.py +0 -107
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
- {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, '📄')
|