django-cfg 1.4.10__py3-none-any.whl → 1.4.13__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 (225) 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/views/api/currencies.py +49 -6
  8. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  9. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  10. django_cfg/apps/payments/views/overview/views.py +2 -1
  11. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  12. django_cfg/apps/urls.py +106 -45
  13. django_cfg/core/base/config_model.py +2 -2
  14. django_cfg/core/constants.py +1 -1
  15. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  16. django_cfg/core/generation/integration_generators/api.py +73 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/management/commands/check_endpoints.py +11 -160
  20. django_cfg/management/commands/check_settings.py +13 -348
  21. django_cfg/management/commands/clear_constance.py +13 -201
  22. django_cfg/management/commands/create_token.py +13 -321
  23. django_cfg/management/commands/generate_clients.py +23 -0
  24. django_cfg/management/commands/list_urls.py +13 -306
  25. django_cfg/management/commands/migrate_all.py +13 -126
  26. django_cfg/management/commands/migrator.py +13 -396
  27. django_cfg/management/commands/rundramatiq.py +15 -247
  28. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  29. django_cfg/management/commands/runserver_ngrok.py +15 -160
  30. django_cfg/management/commands/script.py +12 -488
  31. django_cfg/management/commands/show_config.py +12 -215
  32. django_cfg/management/commands/show_urls.py +12 -342
  33. django_cfg/management/commands/superuser.py +15 -295
  34. django_cfg/management/commands/task_clear.py +14 -217
  35. django_cfg/management/commands/task_status.py +13 -248
  36. django_cfg/management/commands/test_email.py +15 -86
  37. django_cfg/management/commands/test_telegram.py +14 -61
  38. django_cfg/management/commands/test_twilio.py +15 -105
  39. django_cfg/management/commands/tree.py +13 -383
  40. django_cfg/management/commands/validate_openapi.py +10 -0
  41. django_cfg/middleware/README.md +1 -1
  42. django_cfg/middleware/user_activity.py +3 -3
  43. django_cfg/models/__init__.py +2 -2
  44. django_cfg/models/api/drf/spectacular.py +6 -6
  45. django_cfg/models/django/__init__.py +2 -2
  46. django_cfg/models/django/openapi.py +162 -0
  47. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  48. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  49. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  50. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  51. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  52. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  53. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  54. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  55. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  56. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  57. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  58. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  59. django_cfg/modules/django_client/__init__.py +20 -0
  60. django_cfg/modules/django_client/apps.py +35 -0
  61. django_cfg/modules/django_client/core/__init__.py +56 -0
  62. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  63. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  64. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  65. django_cfg/modules/django_client/core/cli/main.py +235 -0
  66. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  67. django_cfg/modules/django_client/core/config/config.py +208 -0
  68. django_cfg/modules/django_client/core/config/group.py +101 -0
  69. django_cfg/modules/django_client/core/config/service.py +209 -0
  70. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  71. django_cfg/modules/django_client/core/generator/base.py +838 -0
  72. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  73. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  74. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  75. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  76. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  77. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  78. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  79. django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
  80. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
  81. django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
  82. django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
  83. django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
  84. django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
  85. django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
  86. django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
  87. django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
  88. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  89. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  90. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  91. django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
  92. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
  93. django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
  94. django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
  95. django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
  96. django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
  97. django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
  98. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  99. django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
  100. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  101. django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
  102. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  103. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  104. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  105. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  106. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  107. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  108. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  109. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  110. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  111. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  112. django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
  113. django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
  114. django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
  115. django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
  116. django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
  117. django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
  118. django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
  119. django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
  120. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  121. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  122. django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
  123. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
  124. django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
  125. django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
  126. django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
  127. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  128. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  129. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  130. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  131. django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
  132. django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
  133. django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
  134. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  135. django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
  136. django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
  137. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  138. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  139. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  140. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  141. django_cfg/modules/django_client/core/ir/context.py +387 -0
  142. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  143. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  144. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  145. django_cfg/modules/django_client/core/parser/base.py +648 -0
  146. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  147. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  148. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  149. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  150. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  151. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  152. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  153. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  154. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  155. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  156. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  157. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  158. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  159. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  160. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  161. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  162. django_cfg/modules/django_client/management/__init__.py +3 -0
  163. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  164. django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
  165. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  166. django_cfg/modules/django_client/pytest.ini +30 -0
  167. django_cfg/modules/django_client/spectacular/__init__.py +10 -0
  168. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  169. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  170. django_cfg/modules/django_client/urls.py +72 -0
  171. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  172. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  173. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  174. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  175. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  176. django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
  177. django_cfg/modules/django_email/management/__init__.py +0 -0
  178. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  179. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  180. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  181. django_cfg/modules/django_logging/django_logger.py +6 -6
  182. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  183. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  184. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  185. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  186. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  187. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  188. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  189. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  190. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  191. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  192. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  193. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  194. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  195. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  196. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  197. django_cfg/modules/django_unfold/callbacks/main.py +21 -10
  198. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  199. django_cfg/pyproject.toml +2 -6
  200. django_cfg/registry/third_party.py +5 -7
  201. django_cfg/routing/callbacks.py +1 -1
  202. django_cfg/static/admin/css/prose-unfold.css +666 -0
  203. django_cfg/templates/admin/index.html +8 -0
  204. django_cfg/templates/admin/index_new.html +13 -0
  205. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  206. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  207. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  208. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
  209. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
  210. django_cfg/management/commands/generate.py +0 -107
  211. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  212. /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
  213. /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
  214. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  215. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  216. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  217. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  218. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  219. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  220. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  221. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  222. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  223. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  224. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  225. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -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