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