django-cfg 1.5.8__py3-none-any.whl → 1.5.14__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.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (119) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/commands/serializers.py +152 -0
  3. django_cfg/apps/api/commands/views.py +32 -0
  4. django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
  5. django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
  6. django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
  7. django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
  8. django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
  9. django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
  10. django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
  11. django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
  12. django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
  13. django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
  14. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +5 -5
  15. django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
  16. django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
  17. django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
  18. django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
  19. django_cfg/apps/integrations/grpc/admin/config.py +113 -9
  20. django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
  21. django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
  22. django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
  23. django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
  24. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
  25. django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
  26. django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
  27. django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
  28. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +130 -0
  29. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +171 -96
  30. django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
  31. django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
  32. django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
  33. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
  34. django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
  35. django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
  36. django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
  37. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
  38. django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
  39. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
  40. django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
  41. django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
  42. django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
  43. django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
  44. django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
  45. django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
  46. django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
  47. django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
  48. django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
  49. django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
  50. django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
  51. django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
  52. django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
  53. django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
  54. django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
  55. django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
  56. django_cfg/apps/integrations/grpc/urls.py +8 -0
  57. django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
  58. django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
  59. django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
  60. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +177 -0
  61. django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
  62. django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
  63. django_cfg/apps/integrations/grpc/views/charts.py +21 -14
  64. django_cfg/apps/integrations/grpc/views/config.py +8 -6
  65. django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
  66. django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
  67. django_cfg/apps/integrations/grpc/views/services.py +30 -21
  68. django_cfg/apps/integrations/grpc/views/testing.py +45 -43
  69. django_cfg/apps/integrations/rq/views/jobs.py +19 -9
  70. django_cfg/apps/integrations/rq/views/schedule.py +7 -3
  71. django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
  72. django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
  73. django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
  74. django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
  75. django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
  76. django_cfg/config.py +33 -0
  77. django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
  78. django_cfg/management/commands/check_endpoints.py +2 -2
  79. django_cfg/management/commands/check_settings.py +3 -10
  80. django_cfg/management/commands/clear_constance.py +3 -10
  81. django_cfg/management/commands/create_token.py +4 -11
  82. django_cfg/management/commands/list_urls.py +4 -10
  83. django_cfg/management/commands/migrate_all.py +18 -12
  84. django_cfg/management/commands/migrator.py +4 -11
  85. django_cfg/management/commands/script.py +4 -10
  86. django_cfg/management/commands/show_config.py +8 -16
  87. django_cfg/management/commands/show_urls.py +5 -11
  88. django_cfg/management/commands/superuser.py +4 -11
  89. django_cfg/management/commands/tree.py +5 -10
  90. django_cfg/management/utils/README.md +402 -0
  91. django_cfg/management/utils/__init__.py +29 -0
  92. django_cfg/management/utils/mixins.py +176 -0
  93. django_cfg/middleware/pagination.py +53 -54
  94. django_cfg/models/api/grpc/__init__.py +15 -21
  95. django_cfg/models/api/grpc/config.py +155 -73
  96. django_cfg/models/ngrok/config.py +7 -6
  97. django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
  98. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
  99. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
  100. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
  101. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
  102. django_cfg/modules/django_client/core/parser/base.py +114 -30
  103. django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
  104. django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
  105. django_cfg/modules/django_email/management/commands/test_email.py +4 -10
  106. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
  107. django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
  108. django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
  109. django_cfg/modules/django_unfold/navigation.py +6 -18
  110. django_cfg/pyproject.toml +1 -1
  111. django_cfg/registry/modules.py +1 -4
  112. django_cfg/requirements.txt +52 -0
  113. django_cfg/static/frontend/admin.zip +0 -0
  114. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/METADATA +1 -1
  115. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/RECORD +118 -97
  116. django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
  117. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/WHEEL +0 -0
  118. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/entry_points.txt +0 -0
  119. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/licenses/LICENSE +0 -0
@@ -5,16 +5,18 @@ Handles automatic start/stop of scheduled maintenance windows.
5
5
  """
6
6
 
7
7
 
8
- from django.core.management.base import BaseCommand
9
8
  from django.utils import timezone
10
9
 
10
+ from django_cfg.management.utils import AdminCommand
11
+
11
12
  from ...models import ScheduledMaintenance
12
13
  from ...services.scheduled_maintenance_service import scheduled_maintenance_service
13
14
 
14
15
 
15
- class Command(BaseCommand):
16
+ class Command(AdminCommand):
16
17
  """Process scheduled maintenance events."""
17
18
 
19
+ command_name = 'process_scheduled_maintenance'
18
20
  help = 'Process scheduled maintenance events (start due, complete overdue)'
19
21
 
20
22
  def add_arguments(self, parser):
@@ -5,16 +5,19 @@ Automatically discovers and syncs Cloudflare zones with Django models.
5
5
  """
6
6
 
7
7
 
8
- from django.core.management.base import BaseCommand, CommandError
8
+ from django.core.management.base import CommandError
9
9
  from django.utils import timezone
10
10
 
11
+ from django_cfg.management.utils import AdminCommand
12
+
11
13
  from ...models import CloudflareApiKey
12
14
  from ...services.site_sync_service import SiteSyncService
13
15
 
14
16
 
15
- class Command(BaseCommand):
17
+ class Command(AdminCommand):
16
18
  """Sync sites with Cloudflare zones."""
17
19
 
20
+ command_name = 'sync_cloudflare'
18
21
  help = 'Sync CloudflareSite models with actual Cloudflare zones'
19
22
 
20
23
  def add_arguments(self, parser):
django_cfg/config.py CHANGED
@@ -9,6 +9,39 @@ from typing import List
9
9
  from .modules.django_admin.icons import Icons
10
10
  from .modules.django_unfold.models.dropdown import SiteDropdownItem
11
11
 
12
+
13
+ def is_feature_available(feature: str) -> bool:
14
+ """
15
+ Check if a feature is available (dependencies installed).
16
+
17
+ Args:
18
+ feature: Feature name (e.g., 'grpc', 'centrifugo', 'dramatiq')
19
+
20
+ Returns:
21
+ True if feature dependencies are available
22
+ """
23
+ if feature == "grpc":
24
+ try:
25
+ import grpc # noqa
26
+ import grpc_reflection # noqa
27
+ return True
28
+ except ImportError:
29
+ return False
30
+ elif feature == "centrifugo":
31
+ try:
32
+ import cent # noqa
33
+ return True
34
+ except ImportError:
35
+ return False
36
+ elif feature == "dramatiq":
37
+ try:
38
+ import dramatiq # noqa
39
+ return True
40
+ except ImportError:
41
+ return False
42
+
43
+ return False
44
+
12
45
  # Library configuration
13
46
  LIB_NAME = "django-cfg"
14
47
  LIB_SITE_URL = "https://djangocfg.com"
@@ -119,9 +119,10 @@ class GRPCSettingsGenerator:
119
119
  if grpc_proto:
120
120
  settings["GRPC_PROTO"] = grpc_proto
121
121
 
122
- logger.info("✅ gRPC framework enabled")
122
+ logger.info("✅ gRPC framework enabled (async)")
123
123
  logger.info(f" - Server: {self.config.grpc.server.host}:{self.config.grpc.server.port}")
124
- logger.info(f" - Workers: {self.config.grpc.server.max_workers}")
124
+ max_streams = self.config.grpc.server.max_concurrent_streams or "unlimited"
125
+ logger.info(f" - Max concurrent streams: {max_streams}")
125
126
  logger.info(f" - Auth: {'enabled' if self.config.grpc.auth.enabled else 'disabled'}")
126
127
  logger.info(f" - Reflection: {'enabled' if self.config.grpc.server.enable_reflection else 'disabled'}")
127
128
 
@@ -169,7 +170,8 @@ class GRPCSettingsGenerator:
169
170
  server_settings = {
170
171
  "host": server_config.host,
171
172
  "port": server_config.port,
172
- "max_workers": server_config.max_workers,
173
+ "max_concurrent_streams": server_config.max_concurrent_streams,
174
+ "asyncio_debug": server_config.asyncio_debug,
173
175
  "enable_reflection": server_config.enable_reflection,
174
176
  "enable_health_check": server_config.enable_health_check,
175
177
  "max_send_message_length": server_config.max_send_message_length,
@@ -200,21 +202,11 @@ class GRPCSettingsGenerator:
200
202
  auth_settings = {
201
203
  "enabled": auth_config.enabled,
202
204
  "require_auth": auth_config.require_auth,
203
- "token_header": auth_config.token_header,
204
- "token_prefix": auth_config.token_prefix,
205
- "jwt_algorithm": auth_config.jwt_algorithm,
206
- "jwt_verify_exp": auth_config.jwt_verify_exp,
207
- "jwt_leeway": auth_config.jwt_leeway,
205
+ "api_key_header": auth_config.api_key_header,
206
+ "accept_django_secret_key": auth_config.accept_django_secret_key,
208
207
  "public_methods": auth_config.public_methods,
209
208
  }
210
209
 
211
- # Use JWT secret from auth config or fall back to Django SECRET_KEY
212
- if auth_config.jwt_secret_key:
213
- auth_settings["jwt_secret_key"] = auth_config.jwt_secret_key
214
- else:
215
- # Will be resolved from Django settings at runtime
216
- auth_settings["jwt_secret_key"] = None # Signal to use Django SECRET_KEY
217
-
218
210
  return auth_settings
219
211
 
220
212
  def _build_grpc_proto_settings(self) -> Dict[str, Any]:
@@ -240,10 +232,13 @@ class GRPCSettingsGenerator:
240
232
  """
241
233
  Build list of server interceptors.
242
234
 
243
- Interceptors are added in order:
244
- 1. Request logger interceptor (always enabled for monitoring)
245
- 2. Logging interceptor (if dev mode)
246
- 3. Auth interceptor (if auth enabled)
235
+ IMPORTANT: Interceptors are executed in reverse order for requests!
236
+ The first interceptor in the list wraps all others.
237
+
238
+ Correct order for our use case:
239
+ 1. Auth interceptor (MUST be first to set context.api_key before logging)
240
+ 2. Request logger interceptor (uses context.api_key from auth)
241
+ 3. Logging interceptor (if dev mode)
247
242
  4. Metrics interceptor (if dev mode)
248
243
  5. Custom interceptors (from config)
249
244
 
@@ -255,30 +250,33 @@ class GRPCSettingsGenerator:
255
250
  # Check if we're in dev mode
256
251
  is_dev = self.config.env_mode in ("local", "development", "dev")
257
252
 
258
- # Add request logger interceptor (always enabled for DB logging)
259
- interceptors.append(
260
- "django_cfg.apps.integrations.grpc.interceptors.RequestLoggerInterceptor"
261
- )
253
+ # NOTE: Interceptors are applied in REVERSE order (last added = first executed)!
254
+ # So add them in reverse order of execution:
262
255
 
263
- # Add logging interceptor in dev mode
256
+ # 4. Add metrics interceptor in dev mode (executed LAST)
264
257
  if is_dev:
265
258
  interceptors.append(
266
- "django_cfg.apps.integrations.grpc.interceptors.LoggingInterceptor"
259
+ "django_cfg.apps.integrations.grpc.interceptors.MetricsInterceptor"
267
260
  )
268
261
 
269
- # Add auth interceptor if enabled
270
- if self.config.grpc.auth.enabled:
262
+ # 3. Add logging interceptor in dev mode (executed 3rd)
263
+ if is_dev:
271
264
  interceptors.append(
272
- "django_cfg.apps.integrations.grpc.auth.JWTAuthInterceptor"
265
+ "django_cfg.apps.integrations.grpc.interceptors.LoggingInterceptor"
273
266
  )
274
267
 
275
- # Add metrics interceptor in dev mode
276
- if is_dev:
268
+ # 2. Add request logger interceptor (executed 2nd - needs context vars from auth)
269
+ interceptors.append(
270
+ "django_cfg.apps.integrations.grpc.interceptors.RequestLoggerInterceptor"
271
+ )
272
+
273
+ # 1. Add auth interceptor LAST in list (executed FIRST - sets contextvars!)
274
+ if self.config.grpc.auth.enabled:
277
275
  interceptors.append(
278
- "django_cfg.apps.integrations.grpc.interceptors.MetricsInterceptor"
276
+ "django_cfg.apps.integrations.grpc.auth.ApiKeyAuthInterceptor"
279
277
  )
280
278
 
281
- # Add custom interceptors from server config
279
+ # 5. Add custom interceptors from server config
282
280
  if self.config.grpc.server.interceptors:
283
281
  interceptors.extend(self.config.grpc.server.interceptors)
284
282
 
@@ -10,13 +10,13 @@ Usage:
10
10
 
11
11
  import json
12
12
 
13
- from django.core.management.base import BaseCommand
14
13
  from django.urls import reverse
15
14
 
16
15
  from django_cfg.apps.api.endpoints.endpoints_status.checker import check_all_endpoints
16
+ from django_cfg.management.utils import SafeCommand
17
17
 
18
18
 
19
- class Command(BaseCommand):
19
+ class Command(SafeCommand):
20
20
  help = 'Check status of all Django CFG API endpoints'
21
21
 
22
22
  def add_arguments(self, parser):
@@ -9,20 +9,13 @@ import os
9
9
 
10
10
  from django.conf import settings
11
11
  from django.core.mail import get_connection
12
- from django.core.management.base import BaseCommand
13
12
 
14
- from django_cfg.modules.django_logging import get_logger
13
+ from django_cfg.management.utils import SafeCommand
15
14
 
16
- logger = get_logger('check_settings')
17
15
 
18
- class Command(BaseCommand):
16
+ class Command(SafeCommand):
19
17
  """Command to check and debug django-cfg settings."""
20
18
 
21
- # Web execution metadata
22
- web_executable = True
23
- requires_input = False
24
- is_destructive = False
25
-
26
19
  help = "Check and debug django-cfg configuration settings"
27
20
 
28
21
  def add_arguments(self, parser):
@@ -39,7 +32,7 @@ class Command(BaseCommand):
39
32
 
40
33
  def handle(self, *args, **options):
41
34
  """Main command handler."""
42
- logger.info("Starting check_settings command")
35
+ self.logger.info("Starting check_settings command")
43
36
  self.stdout.write(self.style.SUCCESS("\n🔍 Django CFG Settings Checker\n"))
44
37
 
45
38
  # Show basic info
@@ -5,19 +5,12 @@ Clear Constance configuration cache and database records.
5
5
 
6
6
  from django.conf import settings
7
7
  from django.core.cache import cache
8
- from django.core.management.base import BaseCommand
9
8
 
10
- from django_cfg.modules.django_logging import get_logger
9
+ from django_cfg.management.utils import DestructiveCommand
11
10
 
12
11
 
13
- class Command(BaseCommand):
14
- logger = get_logger('clear_constance')
15
-
16
- # Web execution metadata
17
- web_executable = False
18
- requires_input = True
19
- is_destructive = True
20
-
12
+ class Command(DestructiveCommand):
13
+ command_name = 'clear_constance'
21
14
  help = 'Clear Constance configuration cache and database records'
22
15
 
23
16
  def add_arguments(self, parser):
@@ -10,21 +10,14 @@ from pathlib import Path
10
10
 
11
11
  import questionary
12
12
  from django.contrib.auth import get_user_model
13
- from django.core.management.base import BaseCommand
14
13
 
15
- from django_cfg.modules.django_logging import get_logger
14
+ from django_cfg.management.utils import InteractiveCommand
16
15
 
17
16
  User = get_user_model()
18
17
 
19
18
 
20
- logger = get_logger('create_token')
21
-
22
- class Command(BaseCommand):
23
- # Web execution metadata
24
- web_executable = False
25
- requires_input = True
26
- is_destructive = False
27
-
19
+ class Command(InteractiveCommand):
20
+ command_name = 'create_token'
28
21
  help = 'Create API tokens and authentication tokens'
29
22
 
30
23
  def add_arguments(self, parser):
@@ -52,7 +45,7 @@ class Command(BaseCommand):
52
45
  )
53
46
 
54
47
  def handle(self, *args, **options):
55
- logger.info("Starting create_token command")
48
+ self.logger.info("Starting create_token command")
56
49
  if options['user'] and options['type']:
57
50
  self.create_token_for_user(
58
51
  username=options['user'],
@@ -8,7 +8,6 @@ Useful for development and webhook configuration.
8
8
  import re
9
9
 
10
10
  from django.conf import settings
11
- from django.core.management.base import BaseCommand
12
11
  from django.urls import get_resolver
13
12
  from rich.align import Align
14
13
 
@@ -19,18 +18,13 @@ from rich.table import Table
19
18
  from rich.text import Text
20
19
 
21
20
  from django_cfg.core.state import get_current_config
22
- from django_cfg.modules.django_logging import get_logger
21
+ from django_cfg.management.utils import SafeCommand
23
22
 
24
- logger = get_logger('list_urls')
25
23
 
26
- class Command(BaseCommand):
24
+ class Command(SafeCommand):
27
25
  """Command to display all available URLs in the project."""
28
26
 
29
- # Web execution metadata
30
- web_executable = True
31
- requires_input = False
32
- is_destructive = False
33
-
27
+ command_name = 'list_urls'
34
28
  help = "Display all available URLs with Rich formatting"
35
29
 
36
30
  def __init__(self, *args, **kwargs):
@@ -62,7 +56,7 @@ class Command(BaseCommand):
62
56
  )
63
57
 
64
58
  def handle(self, *args, **options):
65
- logger.info("Starting list_urls command")
59
+ self.logger.info("Starting list_urls command")
66
60
  filter_str = options["filter"]
67
61
  webhook_only = options["webhook"]
68
62
  api_only = options["api"]
@@ -5,18 +5,24 @@ Migrate all databases based on django-cfg configuration.
5
5
 
6
6
  from django.apps import apps
7
7
  from django.core.management import call_command
8
- from django.core.management.base import BaseCommand
9
8
 
10
9
  from django_cfg.core.state import get_current_config
11
- from django_cfg.modules.django_logging import get_logger
10
+ from django_cfg.management.utils import AdminCommand
12
11
 
13
- logger = get_logger('migrate_all')
14
12
 
15
- class Command(BaseCommand):
16
- # Web execution metadata
17
- web_executable = False
18
- requires_input = False
19
- is_destructive = True
13
+ class Command(AdminCommand):
14
+ """
15
+ Migrate all databases - destructive but non-interactive admin command.
16
+
17
+ This command is marked as destructive because it modifies the database schema,
18
+ but it doesn't require user input during execution.
19
+ """
20
+
21
+ command_name = 'migrate_all'
22
+
23
+ # Override AdminCommand defaults
24
+ web_executable = False # Too risky for web execution
25
+ is_destructive = True # Modifies database schema
20
26
 
21
27
  help = "Migrate all databases based on django-cfg configuration"
22
28
 
@@ -34,7 +40,7 @@ class Command(BaseCommand):
34
40
 
35
41
  def handle(self, *args, **options):
36
42
  """Run migrations for all configured databases."""
37
- logger.info("Starting migrate_all command")
43
+ self.logger.info("Starting migrate_all command")
38
44
  dry_run = options.get("dry_run", False)
39
45
  skip_makemigrations = options.get("skip_makemigrations", False)
40
46
 
@@ -76,7 +82,7 @@ class Command(BaseCommand):
76
82
  call_command("migrate", app_label, database=db_name, verbosity=1)
77
83
  except Exception as e:
78
84
  self.stdout.write(self.style.ERROR(f" ❌ Migration failed for {app_label} on {db_name}: {e}"))
79
- logger.error(f"Migration failed for {app_label} on {db_name}: {e}")
85
+ self.logger.error(f"Migration failed for {app_label} on {db_name}: {e}")
80
86
  raise SystemExit(1)
81
87
  else:
82
88
  self.stdout.write(f" Would run: migrate {app_label} --database={db_name}")
@@ -88,7 +94,7 @@ class Command(BaseCommand):
88
94
  call_command("migrate", database=db_name, verbosity=1)
89
95
  except Exception as e:
90
96
  self.stdout.write(self.style.ERROR(f" ❌ Migration failed for all apps on {db_name}: {e}"))
91
- logger.error(f"Migration failed for all apps on {db_name}: {e}")
97
+ self.logger.error(f"Migration failed for all apps on {db_name}: {e}")
92
98
  raise SystemExit(1)
93
99
  else:
94
100
  self.stdout.write(f" Would run: migrate --database={db_name}")
@@ -100,7 +106,7 @@ class Command(BaseCommand):
100
106
  call_command("migrate", "constance", database="default", verbosity=1)
101
107
  except Exception as e:
102
108
  self.stdout.write(self.style.ERROR(f"❌ Constance migration failed: {e}"))
103
- logger.error(f"Constance migration failed: {e}")
109
+ self.logger.error(f"Constance migration failed: {e}")
104
110
  raise SystemExit(1)
105
111
  else:
106
112
  self.stdout.write(" Would run: migrate constance --database=default")
@@ -9,22 +9,15 @@ import questionary
9
9
  from django.apps import apps
10
10
  from django.conf import settings
11
11
  from django.core.management import call_command
12
- from django.core.management.base import BaseCommand
13
12
  from django.db import connections
14
13
  from django.db.migrations.recorder import MigrationRecorder
15
14
 
16
15
  from django_cfg.core.config import DEFAULT_APPS
17
- from django_cfg.modules.django_logging import get_logger
16
+ from django_cfg.management.utils import DestructiveCommand
18
17
 
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
18
 
19
+ class Command(DestructiveCommand):
20
+ command_name = 'migrator'
28
21
  help = "Smart migration command with interactive menu for multiple databases"
29
22
 
30
23
  def add_arguments(self, parser):
@@ -132,7 +125,7 @@ class Command(BaseCommand):
132
125
 
133
126
  def _raise_system_exit(self, message):
134
127
  self.stdout.write(self.style.ERROR(f"❌ {message}"))
135
- logger.error(message)
128
+ self.logger.error(message)
136
129
  # raise SystemExit(1)
137
130
 
138
131
  def migrate_database(self, db_name):
@@ -10,18 +10,12 @@ from pathlib import Path
10
10
  import questionary
11
11
  from django.conf import settings
12
12
  from django.core.management import call_command
13
- from django.core.management.base import BaseCommand
14
-
15
- from django_cfg.modules.django_logging import get_logger
16
13
 
17
- logger = get_logger('script')
14
+ from django_cfg.management.utils import InteractiveCommand
18
15
 
19
- class Command(BaseCommand):
20
- # Web execution metadata
21
- web_executable = False
22
- requires_input = True
23
- is_destructive = False
24
16
 
17
+ class Command(InteractiveCommand):
18
+ command_name = 'script'
25
19
  help = 'Run custom scripts and manage Django applications'
26
20
 
27
21
  def add_arguments(self, parser):
@@ -52,7 +46,7 @@ class Command(BaseCommand):
52
46
  )
53
47
 
54
48
  def handle(self, *args, **options):
55
- logger.info("Starting script command")
49
+ self.logger.info("Starting script command")
56
50
  if options['list']:
57
51
  self.list_scripts()
58
52
  elif options['create']:
@@ -10,19 +10,11 @@ import json
10
10
  import os
11
11
 
12
12
  from django.conf import settings
13
- from django.core.management.base import BaseCommand
14
13
 
15
- from django_cfg.modules.django_logging import get_logger
14
+ from django_cfg.management.utils import SafeCommand
16
15
 
17
- logger = get_logger('show_config')
18
-
19
-
20
- class Command(BaseCommand):
21
- # Web execution metadata
22
- web_executable = True
23
- requires_input = False
24
- is_destructive = False
25
16
 
17
+ class Command(SafeCommand):
26
18
  help = 'Show Django Config configuration'
27
19
 
28
20
  def add_arguments(self, parser):
@@ -40,23 +32,23 @@ class Command(BaseCommand):
40
32
 
41
33
  def handle(self, *args, **options):
42
34
  """Show configuration in requested format."""
43
- logger.info("Starting show_config command")
35
+ self.logger.info("Starting show_config command")
44
36
  try:
45
37
  # Get the config instance from Django settings
46
38
  config = self._get_config_instance()
47
- logger.info("Successfully retrieved configuration instance")
39
+ self.logger.info("Successfully retrieved configuration instance")
48
40
 
49
41
  if options['format'] == 'json':
50
- logger.info("Displaying configuration in JSON format")
42
+ self.logger.info("Displaying configuration in JSON format")
51
43
  self._show_json_format(config, options['include_secrets'])
52
44
  else:
53
- logger.info("Displaying configuration in table format")
45
+ self.logger.info("Displaying configuration in table format")
54
46
  self._show_table_format(config, options['include_secrets'])
55
47
 
56
- logger.info("show_config command completed successfully")
48
+ self.logger.info("show_config command completed successfully")
57
49
  except Exception as e:
58
50
  error_msg = f'Failed to show configuration: {e}'
59
- logger.error(error_msg, exc_info=True)
51
+ self.logger.error(error_msg, exc_info=True)
60
52
  self.stdout.write(
61
53
  self.style.ERROR(f'❌ {error_msg}')
62
54
  )
@@ -6,16 +6,14 @@ import re
6
6
  from typing import List, Optional, Tuple
7
7
 
8
8
  from django.conf import settings
9
- from django.core.management.base import BaseCommand, CommandParser
9
+ from django.core.management.base import CommandParser
10
10
  from django.urls import get_resolver
11
11
  from django.utils.termcolors import make_style
12
12
 
13
- from django_cfg.modules.django_logging import get_logger
13
+ from django_cfg.management.utils import SafeCommand
14
14
 
15
- logger = get_logger('show_urls')
16
15
 
17
-
18
- class Command(BaseCommand):
16
+ class Command(SafeCommand):
19
17
  """
20
18
  Display all URL patterns in the Django project.
21
19
 
@@ -23,11 +21,7 @@ class Command(BaseCommand):
23
21
  in a hierarchical format with colors and filtering options.
24
22
  """
25
23
 
26
- # Web execution metadata
27
- web_executable = True
28
- requires_input = False
29
- is_destructive = False
30
-
24
+ command_name = 'show_urls'
31
25
  help = 'Display all URL patterns in the project'
32
26
 
33
27
  def __init__(self, *args, **kwargs):
@@ -88,7 +82,7 @@ class Command(BaseCommand):
88
82
 
89
83
  def handle(self, *args, **options) -> None:
90
84
  """Main command handler."""
91
- logger.info("Starting show_urls command")
85
+ self.logger.info("Starting show_urls command")
92
86
  self.options = options
93
87
 
94
88
  # Disable colors if requested
@@ -7,22 +7,15 @@ import questionary
7
7
  from django.contrib.auth import get_user_model
8
8
  from django.core.exceptions import ValidationError
9
9
  from django.core.management import call_command
10
- from django.core.management.base import BaseCommand
11
10
  from django.core.validators import validate_email
12
11
 
13
- from django_cfg.modules.django_logging import get_logger
12
+ from django_cfg.management.utils import InteractiveCommand
14
13
 
15
14
  User = get_user_model()
16
15
 
17
16
 
18
- logger = get_logger('superuser')
19
-
20
- class Command(BaseCommand):
21
- # Web execution metadata
22
- web_executable = False
23
- requires_input = True
24
- is_destructive = False
25
-
17
+ class Command(InteractiveCommand):
18
+ command_name = 'superuser'
26
19
  help = 'Create a superuser with enhanced validation and configuration'
27
20
 
28
21
  def add_arguments(self, parser):
@@ -58,7 +51,7 @@ class Command(BaseCommand):
58
51
  )
59
52
 
60
53
  def handle(self, *args, **options):
61
- logger.info("Starting superuser command")
54
+ self.logger.info("Starting superuser command")
62
55
  if options['interactive'] or not any([options['username'], options['email'], options['password']]):
63
56
  self.create_superuser_interactive()
64
57
  else:
@@ -8,22 +8,17 @@ import subprocess
8
8
  from pathlib import Path
9
9
 
10
10
  from django.conf import settings
11
- from django.core.management.base import BaseCommand, CommandError
11
+ from django.core.management.base import CommandError
12
12
 
13
13
  from django_cfg.core.state import get_current_config
14
- from django_cfg.modules.django_logging import get_logger
14
+ from django_cfg.management.utils import SafeCommand
15
15
  from django_cfg.utils.path_resolution import PathResolver
16
16
 
17
- logger = get_logger('tree')
18
17
 
19
- class Command(BaseCommand):
18
+ class Command(SafeCommand):
20
19
  """Display Django project structure in tree format."""
21
20
 
22
- # Web execution metadata
23
- web_executable = True
24
- requires_input = False
25
- is_destructive = False
26
-
21
+ command_name = 'tree'
27
22
  help = "Display Django project structure based on django-cfg configuration"
28
23
 
29
24
  def add_arguments(self, parser):
@@ -83,7 +78,7 @@ class Command(BaseCommand):
83
78
 
84
79
  def handle(self, *args, **options):
85
80
  """Execute the command."""
86
- logger.info("Starting tree command")
81
+ self.logger.info("Starting tree command")
87
82
  try:
88
83
  # Get django-cfg configuration
89
84
  config = get_current_config()