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,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