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
@@ -1,403 +1,20 @@
1
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}")
2
+ Django-CFG wrapper for migrator command.
206
3
 
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"))
4
+ This is a simple alias for django_admin.management.commands.migrator.
5
+ All logic is in django_admin module.
210
6
 
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
7
+ Usage:
8
+ python manage.py migrator
9
+ """
379
10
 
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
11
+ from django_cfg.modules.django_admin.management.commands.migrator import Command as MigratorCommand
387
12
 
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
13
 
397
- # Also check if the app has any registered models
398
- models = app_config.get_models()
399
- return len(models) > 0
14
+ class Command(MigratorCommand):
15
+ """
16
+ Alias for migrator command.
400
17
 
401
- except Exception:
402
- # Silently return False for apps that don't exist or have issues
403
- return False
18
+ Simply inherits from MigratorCommand without any changes.
19
+ """
20
+ pass
@@ -1,254 +1,22 @@
1
1
  """
2
- Django management command for running Dramatiq workers.
2
+ Django-CFG wrapper for rundramatiq command.
3
3
 
4
- Based on django_dramatiq.management.commands.rundramatiq with Django-CFG integration.
5
- Simple, clean, and working approach.
6
- """
7
-
8
- import argparse
9
- import importlib
10
- import multiprocessing
11
- import os
12
- import sys
13
-
14
- from django.apps import apps
15
- from django.conf import settings
16
- from django.core.management.base import BaseCommand
17
- from django.utils.module_loading import module_has_submodule
18
- from django_cfg.modules.django_logging import get_logger
19
- from django_cfg.modules.django_tasks import get_task_service
20
-
21
-
22
- # Default values
23
- NPROCS = multiprocessing.cpu_count()
24
- NTHREADS = 8
4
+ This is a simple alias for django_tasks.management.commands.rundramatiq.
5
+ All logic is in django_tasks module.
25
6
 
7
+ Usage:
8
+ python manage.py rundramatiq
9
+ python manage.py rundramatiq --processes 4 --threads 8
10
+ python manage.py rundramatiq --queues default high_priority
11
+ """
26
12
 
27
- logger = get_logger('rundramatiq')
28
-
29
-
30
- class Command(BaseCommand):
31
- # Web execution metadata
32
- web_executable = False
33
- requires_input = False
34
- is_destructive = False
35
-
36
- help = "Run Dramatiq workers with Django-CFG configuration."
37
-
38
- def add_arguments(self, parser):
39
- parser.formatter_class = argparse.ArgumentDefaultsHelpFormatter
40
-
41
- parser.add_argument(
42
- "--processes", "-p",
43
- default=NPROCS,
44
- type=int,
45
- help="The number of processes to run",
46
- )
47
- parser.add_argument(
48
- "--threads", "-t",
49
- default=NTHREADS,
50
- type=int,
51
- help="The number of threads per process to use",
52
- )
53
- parser.add_argument(
54
- "--queues", "-Q",
55
- nargs="*",
56
- type=str,
57
- help="Listen to a subset of queues, or all when empty",
58
- )
59
- parser.add_argument(
60
- "--watch",
61
- dest="watch_dir",
62
- help="Reload workers when changes are detected in the given directory",
63
- )
64
- parser.add_argument(
65
- "--pid-file",
66
- type=str,
67
- help="Write the PID of the master process to this file",
68
- )
69
- parser.add_argument(
70
- "--log-file",
71
- type=str,
72
- help="Write all logs to a file, or stderr when empty",
73
- )
74
- parser.add_argument(
75
- "--worker-shutdown-timeout",
76
- type=int,
77
- default=600000,
78
- help="Timeout for worker shutdown, in milliseconds"
79
- )
80
- parser.add_argument(
81
- "--dry-run",
82
- action="store_true",
83
- help="Show configuration without starting workers",
84
- )
13
+ from django_cfg.modules.django_tasks.management.commands.rundramatiq import Command as DramatiqCommand
85
14
 
86
- def handle(self, watch_dir, processes, threads, verbosity, queues,
87
- pid_file, log_file, worker_shutdown_timeout, dry_run, **options):
88
- logger.info("Starting rundramatiq command")
89
-
90
- # Get task service and validate
91
- task_service = get_task_service()
92
- if not task_service.is_enabled():
93
- self.stdout.write(
94
- self.style.ERROR("Task system is not enabled in Django-CFG configuration")
95
- )
96
- return
97
-
98
- # Discover task modules
99
- tasks_modules = self._discover_tasks_modules()
100
-
101
- # Show configuration info
102
- self.stdout.write(self.style.SUCCESS("Dramatiq Worker Configuration:"))
103
- self.stdout.write(f"Processes: {processes}")
104
- self.stdout.write(f"Threads: {threads}")
105
- if queues:
106
- self.stdout.write(f"Queues: {', '.join(queues)}")
107
- else:
108
- self.stdout.write("Queues: all")
109
-
110
- self.stdout.write(f"\nDiscovered task modules:")
111
- for module in tasks_modules:
112
- self.stdout.write(f" - {module}")
113
-
114
- # If dry run, show command and exit
115
- if dry_run:
116
- executable_name = "dramatiq"
117
-
118
- process_args = [
119
- executable_name,
120
- "django_cfg.modules.django_tasks.dramatiq_setup", # Broker module
121
- "--processes", str(processes),
122
- "--threads", str(threads),
123
- "--worker-shutdown-timeout", str(worker_shutdown_timeout),
124
- ]
125
-
126
- if watch_dir:
127
- process_args.extend(["--watch", watch_dir])
128
-
129
- verbosity_args = ["-v"] * (verbosity - 1)
130
- process_args.extend(verbosity_args)
131
-
132
- if queues:
133
- process_args.extend(["--queues"] + queues)
134
-
135
- if pid_file:
136
- process_args.extend(["--pid-file", pid_file])
137
-
138
- if log_file:
139
- process_args.extend(["--log-file", log_file])
140
-
141
- # Add task modules (broker module is already first in tasks_modules)
142
- process_args.extend(tasks_modules)
143
-
144
- self.stdout.write(f"\nCommand that would be executed:")
145
- self.stdout.write(f' {" ".join(process_args)}')
146
- return
147
-
148
- # Show startup info
149
- self.stdout.write(self.style.SUCCESS("\nStarting Dramatiq workers..."))
150
-
151
- # Build dramatiq command
152
- executable_name = "dramatiq"
153
- executable_path = self._resolve_executable(executable_name)
154
-
155
- # Build process arguments exactly like django_dramatiq
156
- process_args = [
157
- executable_name,
158
- "django_cfg.modules.django_tasks.dramatiq_setup", # Broker module
159
- "--processes", str(processes),
160
- "--threads", str(threads),
161
- "--worker-shutdown-timeout", str(worker_shutdown_timeout),
162
- ]
163
-
164
- # Add watch directory if specified
165
- if watch_dir:
166
- process_args.extend(["--watch", watch_dir])
167
-
168
- # Add verbosity
169
- verbosity_args = ["-v"] * (verbosity - 1)
170
- process_args.extend(verbosity_args)
171
-
172
- # Add queues if specified
173
- if queues:
174
- process_args.extend(["--queues"] + queues)
175
-
176
- # Add PID file if specified
177
- if pid_file:
178
- process_args.extend(["--pid-file", pid_file])
179
-
180
- # Add log file if specified
181
- if log_file:
182
- process_args.extend(["--log-file", log_file])
183
-
184
- # Add task modules (broker module is already first in tasks_modules)
185
- process_args.extend(tasks_modules)
186
-
187
- self.stdout.write(f'Running dramatiq: "{" ".join(process_args)}"\n')
188
-
189
- # Ensure DJANGO_SETTINGS_MODULE is set for worker processes
190
- if not os.environ.get('DJANGO_SETTINGS_MODULE'):
191
- if hasattr(settings, 'SETTINGS_MODULE'):
192
- os.environ['DJANGO_SETTINGS_MODULE'] = settings.SETTINGS_MODULE
193
- else:
194
- # Try to detect from manage.py or current settings
195
- import django
196
- from django.conf import settings as django_settings
197
- if hasattr(django_settings, '_wrapped') and hasattr(django_settings._wrapped, '__module__'):
198
- module_name = django_settings._wrapped.__module__
199
- os.environ['DJANGO_SETTINGS_MODULE'] = module_name
200
- else:
201
- self.stdout.write(
202
- self.style.WARNING("Could not detect DJANGO_SETTINGS_MODULE")
203
- )
204
-
205
- # Use os.execvp like django_dramatiq to preserve environment
206
- if sys.platform == "win32":
207
- import subprocess
208
- command = [executable_path] + process_args[1:]
209
- sys.exit(subprocess.run(command))
210
-
211
- os.execvp(executable_path, process_args)
212
15
 
213
- def _discover_tasks_modules(self):
214
- """Discover task modules like django_dramatiq does."""
215
- # Always include our broker setup module first
216
- tasks_modules = ["django_cfg.modules.django_tasks.dramatiq_setup"]
217
-
218
- # Get task service for configuration
219
- task_service = get_task_service()
220
-
221
- # Try to get task modules from Django-CFG config
222
- if task_service.config and task_service.config.auto_discover_tasks:
223
- discovered = task_service.discover_tasks()
224
- for module_name in discovered:
225
- self.stdout.write(f"Discovered tasks module: '{module_name}'")
226
- tasks_modules.append(module_name)
227
-
228
- # Fallback: use django_dramatiq discovery logic
229
- if len(tasks_modules) == 1: # Only broker module found
230
- task_module_names = getattr(settings, "DRAMATIQ_AUTODISCOVER_MODULES", ("tasks",))
231
-
232
- for app_config in apps.get_app_configs():
233
- for task_module in task_module_names:
234
- if module_has_submodule(app_config.module, task_module):
235
- module_name = f"{app_config.name}.{task_module}"
236
- try:
237
- importlib.import_module(module_name)
238
- self.stdout.write(f"Discovered tasks module: '{module_name}'")
239
- tasks_modules.append(module_name)
240
- except ImportError:
241
- # Module exists but has import errors, skip it
242
- pass
243
-
244
- return tasks_modules
16
+ class Command(DramatiqCommand):
17
+ """
18
+ Alias for rundramatiq command.
245
19
 
246
- def _resolve_executable(self, exec_name):
247
- """Resolve executable path like django_dramatiq does."""
248
- bin_dir = os.path.dirname(sys.executable)
249
- if bin_dir:
250
- for d in [bin_dir, os.path.join(bin_dir, "Scripts")]:
251
- exec_path = os.path.join(d, exec_name)
252
- if os.path.isfile(exec_path):
253
- return exec_path
254
- return exec_name
20
+ Simply inherits from DramatiqCommand without any changes.
21
+ """
22
+ pass