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,133 @@
|
|
1
|
+
"""
|
2
|
+
Simple Migration Command for Django Config Toolkit
|
3
|
+
Migrate all databases based on django-cfg configuration.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from django.core.management.base import BaseCommand
|
7
|
+
from django.core.management import call_command
|
8
|
+
from django.apps import apps
|
9
|
+
from django_cfg.modules.django_logging import get_logger
|
10
|
+
|
11
|
+
|
12
|
+
from django_cfg.core.state import get_current_config
|
13
|
+
|
14
|
+
|
15
|
+
logger = get_logger('migrate_all')
|
16
|
+
|
17
|
+
class Command(BaseCommand):
|
18
|
+
# Web execution metadata
|
19
|
+
web_executable = False
|
20
|
+
requires_input = False
|
21
|
+
is_destructive = True
|
22
|
+
|
23
|
+
help = "Migrate all databases based on django-cfg configuration"
|
24
|
+
|
25
|
+
def add_arguments(self, parser):
|
26
|
+
parser.add_argument(
|
27
|
+
"--dry-run",
|
28
|
+
action="store_true",
|
29
|
+
help="Show what would be migrated without executing"
|
30
|
+
)
|
31
|
+
parser.add_argument(
|
32
|
+
"--skip-makemigrations",
|
33
|
+
action="store_true",
|
34
|
+
help="Skip makemigrations step"
|
35
|
+
)
|
36
|
+
|
37
|
+
def handle(self, *args, **options):
|
38
|
+
"""Run migrations for all configured databases."""
|
39
|
+
logger.info("Starting migrate_all command")
|
40
|
+
dry_run = options.get("dry_run", False)
|
41
|
+
skip_makemigrations = options.get("skip_makemigrations", False)
|
42
|
+
|
43
|
+
if dry_run:
|
44
|
+
self.stdout.write(self.style.WARNING("🔍 DRY RUN - No changes will be made"))
|
45
|
+
|
46
|
+
self.stdout.write(self.style.SUCCESS("🚀 Migrating all databases..."))
|
47
|
+
|
48
|
+
# Step 1: Create migrations if needed
|
49
|
+
if not skip_makemigrations:
|
50
|
+
self.stdout.write("📝 Creating migrations...")
|
51
|
+
if not dry_run:
|
52
|
+
call_command("makemigrations", verbosity=1)
|
53
|
+
else:
|
54
|
+
self.stdout.write(" Would run: makemigrations")
|
55
|
+
|
56
|
+
# Step 2: Get database configuration
|
57
|
+
try:
|
58
|
+
config = get_current_config()
|
59
|
+
if not config or not hasattr(config, 'databases'):
|
60
|
+
self.stdout.write(self.style.ERROR("❌ No django-cfg configuration found"))
|
61
|
+
return
|
62
|
+
except Exception as e:
|
63
|
+
self.stdout.write(self.style.ERROR(f"❌ Error loading configuration: {e}"))
|
64
|
+
return
|
65
|
+
|
66
|
+
# Step 3: Migrate each database
|
67
|
+
for db_name, db_config in config.databases.items():
|
68
|
+
self.stdout.write(f"\n🔄 Migrating database: {db_name}")
|
69
|
+
|
70
|
+
if hasattr(db_config, 'apps') and db_config.apps:
|
71
|
+
# Migrate specific apps for this database
|
72
|
+
for app_path in db_config.apps:
|
73
|
+
app_label = self._get_app_label(app_path)
|
74
|
+
if app_label:
|
75
|
+
self.stdout.write(f" 📦 Migrating {app_label}...")
|
76
|
+
if not dry_run:
|
77
|
+
try:
|
78
|
+
call_command("migrate", app_label, database=db_name, verbosity=1)
|
79
|
+
except Exception as e:
|
80
|
+
self.stdout.write(self.style.ERROR(f" ❌ Migration failed for {app_label} on {db_name}: {e}"))
|
81
|
+
logger.error(f"Migration failed for {app_label} on {db_name}: {e}")
|
82
|
+
raise SystemExit(1)
|
83
|
+
else:
|
84
|
+
self.stdout.write(f" Would run: migrate {app_label} --database={db_name}")
|
85
|
+
else:
|
86
|
+
# Migrate all apps for this database (usually default)
|
87
|
+
self.stdout.write(f" 📦 Migrating all apps...")
|
88
|
+
if not dry_run:
|
89
|
+
try:
|
90
|
+
call_command("migrate", database=db_name, verbosity=1)
|
91
|
+
except Exception as e:
|
92
|
+
self.stdout.write(self.style.ERROR(f" ❌ Migration failed for all apps on {db_name}: {e}"))
|
93
|
+
logger.error(f"Migration failed for all apps on {db_name}: {e}")
|
94
|
+
raise SystemExit(1)
|
95
|
+
else:
|
96
|
+
self.stdout.write(f" Would run: migrate --database={db_name}")
|
97
|
+
|
98
|
+
# Step 4: Migrate constance if needed
|
99
|
+
self.stdout.write(f"\n🔧 Migrating constance...")
|
100
|
+
if not dry_run:
|
101
|
+
try:
|
102
|
+
call_command("migrate", "constance", database="default", verbosity=1)
|
103
|
+
except Exception as e:
|
104
|
+
self.stdout.write(self.style.ERROR(f"❌ Constance migration failed: {e}"))
|
105
|
+
logger.error(f"Constance migration failed: {e}")
|
106
|
+
raise SystemExit(1)
|
107
|
+
else:
|
108
|
+
self.stdout.write(" Would run: migrate constance --database=default")
|
109
|
+
|
110
|
+
self.stdout.write(self.style.SUCCESS("\n✅ All migrations completed!"))
|
111
|
+
|
112
|
+
def _get_app_label(self, app_path: str) -> str:
|
113
|
+
"""Convert full module path to Django app_label."""
|
114
|
+
try:
|
115
|
+
# Try to get app config by full path first
|
116
|
+
try:
|
117
|
+
app_config = apps.get_app_config(app_path)
|
118
|
+
return app_config.label
|
119
|
+
except LookupError:
|
120
|
+
pass
|
121
|
+
|
122
|
+
# Fallback: extract last part of the path as potential app_label
|
123
|
+
potential_label = app_path.split('.')[-1]
|
124
|
+
try:
|
125
|
+
app_config = apps.get_app_config(potential_label)
|
126
|
+
return app_config.label
|
127
|
+
except LookupError:
|
128
|
+
pass
|
129
|
+
|
130
|
+
return app_path
|
131
|
+
|
132
|
+
except Exception:
|
133
|
+
return app_path
|
@@ -0,0 +1,403 @@
|
|
1
|
+
"""
|
2
|
+
Smart Migration Command for Django Config Toolkit
|
3
|
+
Simple and reliable migration for all databases.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
from pathlib import Path
|
8
|
+
from django.core.management.base import BaseCommand
|
9
|
+
from django.core.management import call_command
|
10
|
+
from django.apps import apps
|
11
|
+
from django.db import connections
|
12
|
+
from django.db.migrations.recorder import MigrationRecorder
|
13
|
+
from django.conf import settings
|
14
|
+
import questionary
|
15
|
+
from datetime import datetime
|
16
|
+
from django_cfg.core.config import DEFAULT_APPS
|
17
|
+
from django_cfg.modules.django_logging import get_logger
|
18
|
+
|
19
|
+
logger = get_logger('migrator')
|
20
|
+
|
21
|
+
|
22
|
+
class Command(BaseCommand):
|
23
|
+
# Web execution metadata
|
24
|
+
web_executable = False
|
25
|
+
requires_input = True
|
26
|
+
is_destructive = True
|
27
|
+
|
28
|
+
help = "Smart migration command with interactive menu for multiple databases"
|
29
|
+
|
30
|
+
def add_arguments(self, parser):
|
31
|
+
parser.add_argument("--auto", action="store_true", help="Run automatic migration without prompts")
|
32
|
+
parser.add_argument("--database", type=str, help="Migrate specific database only")
|
33
|
+
parser.add_argument("--app", type=str, help="Migrate specific app only")
|
34
|
+
|
35
|
+
def handle(self, *args, **options):
|
36
|
+
if options["auto"]:
|
37
|
+
self.run_automatic_migration()
|
38
|
+
elif options["database"]:
|
39
|
+
self.migrate_database(options["database"])
|
40
|
+
elif options["app"]:
|
41
|
+
self.migrate_app(options["app"])
|
42
|
+
else:
|
43
|
+
self.show_interactive_menu()
|
44
|
+
|
45
|
+
def show_interactive_menu(self):
|
46
|
+
"""Show interactive menu with options"""
|
47
|
+
self.stdout.write(self.style.SUCCESS("\n🚀 Smart Migration Tool - Django Config Toolkit\n"))
|
48
|
+
|
49
|
+
databases = self.get_all_database_names()
|
50
|
+
|
51
|
+
choices = [
|
52
|
+
questionary.Choice("🔄 Run Full Migration (All Databases)", value="full"),
|
53
|
+
questionary.Choice("📝 Create Migrations Only", value="makemigrations"),
|
54
|
+
questionary.Choice("🔍 Show Database Status", value="status"),
|
55
|
+
questionary.Choice("⚙️ Show Django Config Info", value="config"),
|
56
|
+
questionary.Choice("❌ Exit", value="exit"),
|
57
|
+
]
|
58
|
+
|
59
|
+
# Add individual database options
|
60
|
+
for db_name in databases:
|
61
|
+
display_name = f"📊 Migrate {db_name.title()} Database Only"
|
62
|
+
choices.insert(-1, questionary.Choice(display_name, value=f"migrate_{db_name}"))
|
63
|
+
|
64
|
+
choice = questionary.select("Select an option:", choices=choices).ask()
|
65
|
+
|
66
|
+
if choice == "full":
|
67
|
+
self.run_full_migration()
|
68
|
+
elif choice == "makemigrations":
|
69
|
+
self.create_migrations()
|
70
|
+
elif choice == "status":
|
71
|
+
self.show_database_status()
|
72
|
+
elif choice == "config":
|
73
|
+
self.show_config_info()
|
74
|
+
elif choice == "exit":
|
75
|
+
self.stdout.write("Goodbye! 👋")
|
76
|
+
return
|
77
|
+
elif choice.startswith("migrate_"):
|
78
|
+
db_name = choice.replace("migrate_", "")
|
79
|
+
self.migrate_database(db_name)
|
80
|
+
|
81
|
+
def run_full_migration(self):
|
82
|
+
"""Run migration for all databases"""
|
83
|
+
self.stdout.write(self.style.SUCCESS("🔄 Starting full migration..."))
|
84
|
+
|
85
|
+
# First migrate default database
|
86
|
+
self.stdout.write("📊 Migrating default database...")
|
87
|
+
self.migrate_database("default")
|
88
|
+
|
89
|
+
# Then migrate other databases (excluding default)
|
90
|
+
databases = self.get_all_database_names()
|
91
|
+
for db_name in databases:
|
92
|
+
if db_name != "default":
|
93
|
+
self.stdout.write(f"🔄 Migrating {db_name}...")
|
94
|
+
self.migrate_database(db_name)
|
95
|
+
|
96
|
+
self.stdout.write(self.style.SUCCESS("✅ Full migration completed!"))
|
97
|
+
|
98
|
+
def run_automatic_migration(self):
|
99
|
+
"""Run automatic migration for all databases"""
|
100
|
+
self.stdout.write(self.style.SUCCESS("🚀 Running automatic migration..."))
|
101
|
+
|
102
|
+
# Create migrations
|
103
|
+
self.create_migrations()
|
104
|
+
|
105
|
+
# Run full migration
|
106
|
+
self.run_full_migration()
|
107
|
+
|
108
|
+
# Always migrate constance (required for django-cfg)
|
109
|
+
self.migrate_constance_if_needed()
|
110
|
+
|
111
|
+
def create_migrations(self):
|
112
|
+
"""Create migrations for all apps"""
|
113
|
+
self.stdout.write(self.style.SUCCESS("📝 Creating migrations..."))
|
114
|
+
|
115
|
+
try:
|
116
|
+
# First try global makemigrations
|
117
|
+
call_command("makemigrations", verbosity=1)
|
118
|
+
|
119
|
+
# Then try for each app that has models but no migrations
|
120
|
+
all_apps = self.get_all_installed_apps()
|
121
|
+
for app in all_apps:
|
122
|
+
if self.app_has_models(app) and not self.app_has_migrations(app):
|
123
|
+
try:
|
124
|
+
self.stdout.write(f" 📝 Creating migrations for {app}...")
|
125
|
+
call_command("makemigrations", app, verbosity=1)
|
126
|
+
except Exception as e:
|
127
|
+
self.stdout.write(self.style.WARNING(f" ⚠️ Could not create migrations for {app}: {e}"))
|
128
|
+
|
129
|
+
self.stdout.write(self.style.SUCCESS("✅ Migrations created"))
|
130
|
+
except Exception as e:
|
131
|
+
self.stdout.write(self.style.WARNING(f"⚠️ Warning creating migrations: {e}"))
|
132
|
+
|
133
|
+
def _raise_system_exit(self, message):
|
134
|
+
self.stdout.write(self.style.ERROR(f"❌ {message}"))
|
135
|
+
logger.error(message)
|
136
|
+
# raise SystemExit(1)
|
137
|
+
|
138
|
+
def migrate_database(self, db_name):
|
139
|
+
"""Migrate specific database"""
|
140
|
+
try:
|
141
|
+
self.stdout.write(f"🔄 Migrating {db_name}...")
|
142
|
+
|
143
|
+
# Get apps for this database
|
144
|
+
apps = self.get_apps_for_database(db_name)
|
145
|
+
|
146
|
+
# Debug info
|
147
|
+
self.stdout.write(f" 📋 Apps for {db_name}: {apps}")
|
148
|
+
|
149
|
+
if not apps:
|
150
|
+
self.stdout.write(self.style.WARNING(f" ⚠️ No apps configured for {db_name}"))
|
151
|
+
return
|
152
|
+
|
153
|
+
# make migrations for all apps
|
154
|
+
self.create_migrations()
|
155
|
+
|
156
|
+
# Migrate each app
|
157
|
+
for app in apps:
|
158
|
+
try:
|
159
|
+
# Skip apps without migrations
|
160
|
+
if not self.app_has_migrations(app):
|
161
|
+
# self.stdout.write(f" ⚠️ Skipping {app} - no migrations")
|
162
|
+
continue
|
163
|
+
|
164
|
+
self.stdout.write(f" 📦 Migrating {app}...")
|
165
|
+
call_command("migrate", app, database=db_name, verbosity=1)
|
166
|
+
except Exception as e:
|
167
|
+
self._raise_system_exit(f"Migration failed for {app} on {db_name}: {e}")
|
168
|
+
|
169
|
+
self.stdout.write(self.style.SUCCESS(f"✅ {db_name} migration completed!"))
|
170
|
+
|
171
|
+
except Exception as e:
|
172
|
+
self._raise_system_exit(f"Error migrating {db_name}: {e}")
|
173
|
+
|
174
|
+
def migrate_constance_if_needed(self):
|
175
|
+
"""Always migrate constance app if it's installed"""
|
176
|
+
try:
|
177
|
+
# Check if constance is in INSTALLED_APPS
|
178
|
+
if 'constance' in settings.INSTALLED_APPS:
|
179
|
+
self.stdout.write(self.style.SUCCESS("🔧 Migrating constance (django-cfg requirement)..."))
|
180
|
+
|
181
|
+
# Try to migrate constance on default database
|
182
|
+
try:
|
183
|
+
call_command("migrate", "constance", database="default", verbosity=1)
|
184
|
+
self.stdout.write(self.style.SUCCESS("✅ Constance migration completed!"))
|
185
|
+
except Exception as e:
|
186
|
+
self._raise_system_exit(f"Constance migration failed: {e}")
|
187
|
+
else:
|
188
|
+
self.stdout.write(self.style.WARNING("⚠️ Constance not found in INSTALLED_APPS"))
|
189
|
+
|
190
|
+
except Exception as e:
|
191
|
+
self._raise_system_exit(f"Could not migrate constance: {e}")
|
192
|
+
|
193
|
+
def migrate_app(self, app_name):
|
194
|
+
"""Migrate specific app across all databases"""
|
195
|
+
self.stdout.write(f"🔄 Migrating app {app_name}...")
|
196
|
+
|
197
|
+
databases = self.get_all_database_names()
|
198
|
+
for db_name in databases:
|
199
|
+
apps = self.get_apps_for_database(db_name)
|
200
|
+
if app_name in apps:
|
201
|
+
self.stdout.write(f" 📊 Migrating {app_name} on {db_name}...")
|
202
|
+
try:
|
203
|
+
call_command("migrate", app_name, database=db_name, verbosity=1)
|
204
|
+
except Exception as e:
|
205
|
+
self._raise_system_exit(f"Migration failed for {app_name} on {db_name}: {e}")
|
206
|
+
|
207
|
+
def show_database_status(self):
|
208
|
+
"""Show status of all databases and their apps"""
|
209
|
+
self.stdout.write(self.style.SUCCESS("\n📊 Database Status Report\n"))
|
210
|
+
|
211
|
+
# Get database info from Django settings
|
212
|
+
db_info = self.get_database_info()
|
213
|
+
databases = self.get_all_database_names()
|
214
|
+
|
215
|
+
for db_name in databases:
|
216
|
+
self.stdout.write(f"\n🗄️ Database: {db_name}")
|
217
|
+
|
218
|
+
# Show database info from Django settings
|
219
|
+
if db_name in db_info:
|
220
|
+
info = db_info[db_name]
|
221
|
+
self.stdout.write(f' 🔧 Engine: {info["engine"]}')
|
222
|
+
self.stdout.write(f' 🔗 Name: {info["name"]}')
|
223
|
+
|
224
|
+
# Test connection
|
225
|
+
try:
|
226
|
+
with connections[db_name].cursor() as cursor:
|
227
|
+
cursor.execute("SELECT 1")
|
228
|
+
self.stdout.write(f" ✅ Connection: OK")
|
229
|
+
except Exception as e:
|
230
|
+
self.stdout.write(f" ❌ Connection: FAILED - {e}")
|
231
|
+
|
232
|
+
# Show apps
|
233
|
+
apps = self.get_apps_for_database(db_name)
|
234
|
+
if apps:
|
235
|
+
self.stdout.write(f' 📦 Apps: {", ".join(apps)}')
|
236
|
+
else:
|
237
|
+
self.stdout.write(f" 📦 Apps: None configured")
|
238
|
+
|
239
|
+
def show_config_info(self):
|
240
|
+
"""Show Django configuration information"""
|
241
|
+
self.stdout.write(self.style.SUCCESS("\n⚙️ Django Configuration Information\n"))
|
242
|
+
|
243
|
+
try:
|
244
|
+
# Environment info
|
245
|
+
self.stdout.write(f'🌍 Environment: {getattr(settings, "ENVIRONMENT", "unknown")}')
|
246
|
+
self.stdout.write(f"🔧 Debug: {settings.DEBUG}")
|
247
|
+
|
248
|
+
# Database info
|
249
|
+
databases = settings.DATABASES
|
250
|
+
self.stdout.write(f"🗄️ Databases: {len(databases)}")
|
251
|
+
|
252
|
+
for db_name, db_config in databases.items():
|
253
|
+
engine = db_config.get("ENGINE", "unknown")
|
254
|
+
name = db_config.get("NAME", "unknown")
|
255
|
+
self.stdout.write(f" 📊 {db_name}: {engine} -> {name}")
|
256
|
+
|
257
|
+
# Multiple databases
|
258
|
+
if len(databases) > 1:
|
259
|
+
self.stdout.write(f"📊 Multiple Databases: Yes")
|
260
|
+
|
261
|
+
# Show routing rules
|
262
|
+
routing_rules = getattr(settings, "DATABASE_ROUTING_RULES", {})
|
263
|
+
if routing_rules:
|
264
|
+
self.stdout.write(f" 🔀 Routing Rules:")
|
265
|
+
for app, db in routing_rules.items():
|
266
|
+
self.stdout.write(f" - {app} → {db}")
|
267
|
+
else:
|
268
|
+
self.stdout.write(f" 🔀 Routing Rules: None configured")
|
269
|
+
else:
|
270
|
+
self.stdout.write(f"📊 Multiple Databases: No")
|
271
|
+
|
272
|
+
# Other settings
|
273
|
+
self.stdout.write(f'🔑 Secret Key: {"*" * 20}...')
|
274
|
+
self.stdout.write(f"🌐 Allowed Hosts: {settings.ALLOWED_HOSTS}")
|
275
|
+
self.stdout.write(f"📦 Installed Apps: {len(settings.INSTALLED_APPS)}")
|
276
|
+
|
277
|
+
except Exception as e:
|
278
|
+
self._raise_system_exit(f"Error getting Django config info: {e}")
|
279
|
+
|
280
|
+
def get_apps_for_database(self, db_name: str):
|
281
|
+
"""Get apps for specific database with smart logic for default"""
|
282
|
+
if db_name == "default":
|
283
|
+
# For default database, get all apps that are not in other databases
|
284
|
+
all_apps = self.get_all_installed_apps()
|
285
|
+
apps_in_other_dbs = self.get_apps_in_other_databases()
|
286
|
+
return [app for app in all_apps if app not in apps_in_other_dbs]
|
287
|
+
else:
|
288
|
+
# For other databases, use configured apps from routing rules
|
289
|
+
routing_rules = getattr(settings, "DATABASE_ROUTING_RULES", {})
|
290
|
+
|
291
|
+
# Routing rules are now in Django settings
|
292
|
+
pass
|
293
|
+
|
294
|
+
return [app for app, db in routing_rules.items() if db == db_name]
|
295
|
+
|
296
|
+
def get_all_installed_apps(self):
|
297
|
+
"""Get all installed Django apps by checking for apps.py files."""
|
298
|
+
apps_list = []
|
299
|
+
|
300
|
+
# Get all Django app configs
|
301
|
+
for app_config in apps.get_app_configs():
|
302
|
+
app_label = app_config.label
|
303
|
+
app_path = Path(app_config.path)
|
304
|
+
|
305
|
+
# Check if apps.py exists in the app directory
|
306
|
+
apps_py_path = app_path / "apps.py"
|
307
|
+
if apps_py_path.exists():
|
308
|
+
if app_label not in DEFAULT_APPS:
|
309
|
+
apps_list.append(app_label)
|
310
|
+
continue
|
311
|
+
|
312
|
+
# Fallback: check if it's a standard Django app (has models.py or admin.py)
|
313
|
+
if (app_path / "models.py").exists() or (app_path / "admin.py").exists():
|
314
|
+
apps_list.append(app_label)
|
315
|
+
|
316
|
+
return apps_list
|
317
|
+
|
318
|
+
def get_apps_in_other_databases(self) -> set:
|
319
|
+
"""Get all apps that are configured for non-default databases."""
|
320
|
+
routing_rules = getattr(settings, "DATABASE_ROUTING_RULES", {})
|
321
|
+
|
322
|
+
# Routing rules are now in Django settings
|
323
|
+
pass
|
324
|
+
|
325
|
+
return set(routing_rules.keys())
|
326
|
+
|
327
|
+
def get_all_database_names(self):
|
328
|
+
"""Get all database names."""
|
329
|
+
return list(connections.databases.keys())
|
330
|
+
|
331
|
+
def get_database_info(self):
|
332
|
+
"""Get database information from Django settings"""
|
333
|
+
try:
|
334
|
+
db_info = {}
|
335
|
+
|
336
|
+
# Get database info from Django settings
|
337
|
+
for db_name, db_config in settings.DATABASES.items():
|
338
|
+
db_info[db_name] = {"name": db_config.get("NAME", "unknown"), "engine": db_config.get("ENGINE", "unknown"), "host": db_config.get("HOST", ""), "port": db_config.get("PORT", ""), "apps": []} # Will be populated by routing logic
|
339
|
+
|
340
|
+
return db_info
|
341
|
+
|
342
|
+
except Exception as e:
|
343
|
+
self.stdout.write(self.style.WARNING(f"⚠️ Error getting database info: {e}"))
|
344
|
+
return {}
|
345
|
+
|
346
|
+
def app_has_migrations(self, app_label: str) -> bool:
|
347
|
+
"""Simple check if an app has migrations."""
|
348
|
+
try:
|
349
|
+
# Get the app config
|
350
|
+
app_config = apps.get_app_config(app_label)
|
351
|
+
if not app_config:
|
352
|
+
return False
|
353
|
+
|
354
|
+
# Check if migrations directory exists and has files
|
355
|
+
migrations_dir = Path(app_config.path) / "migrations"
|
356
|
+
if not migrations_dir.exists():
|
357
|
+
return False
|
358
|
+
|
359
|
+
# Check if there are any migration files (excluding __init__.py)
|
360
|
+
migration_files = [f for f in migrations_dir.glob("*.py") if f.name != "__init__.py"]
|
361
|
+
|
362
|
+
# Also check if there are any applied migrations in the database
|
363
|
+
# Check all databases for this app's migrations
|
364
|
+
for db_name in connections.databases.keys():
|
365
|
+
try:
|
366
|
+
recorder = MigrationRecorder(connections[db_name])
|
367
|
+
applied_migrations = recorder.migration_qs.filter(app=app_label)
|
368
|
+
if applied_migrations.exists():
|
369
|
+
return True
|
370
|
+
except Exception:
|
371
|
+
continue
|
372
|
+
|
373
|
+
# If no applied migrations found, check if there are migration files
|
374
|
+
return len(migration_files) > 0
|
375
|
+
|
376
|
+
except Exception:
|
377
|
+
# Silently return False for apps that don't exist or have issues
|
378
|
+
return False
|
379
|
+
|
380
|
+
def app_has_models(self, app_label: str) -> bool:
|
381
|
+
"""Check if an app has models defined."""
|
382
|
+
try:
|
383
|
+
# Get the app config
|
384
|
+
app_config = apps.get_app_config(app_label)
|
385
|
+
if not app_config:
|
386
|
+
return False
|
387
|
+
|
388
|
+
# Check if models.py exists and has content
|
389
|
+
models_file = Path(app_config.path) / "models.py"
|
390
|
+
if models_file.exists():
|
391
|
+
# Read the file and check if it has model definitions
|
392
|
+
content = models_file.read_text()
|
393
|
+
# Simple check for model definitions
|
394
|
+
if "class " in content and "models.Model" in content:
|
395
|
+
return True
|
396
|
+
|
397
|
+
# Also check if the app has any registered models
|
398
|
+
models = app_config.get_models()
|
399
|
+
return len(models) > 0
|
400
|
+
|
401
|
+
except Exception:
|
402
|
+
# Silently return False for apps that don't exist or have issues
|
403
|
+
return False
|