django-cfg 1.5.20__py3-none-any.whl → 1.5.31__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 (98) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/integrations/centrifugo/__init__.py +2 -0
  3. django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
  4. django_cfg/apps/integrations/centrifugo/services/logging.py +90 -14
  5. django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
  6. django_cfg/apps/integrations/centrifugo/views/testing_api.py +47 -43
  7. django_cfg/apps/integrations/centrifugo/views/wrapper.py +41 -29
  8. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
  9. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +1 -1
  10. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +22 -36
  11. django_cfg/apps/integrations/grpc/managers/grpc_request_log.py +84 -0
  12. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +126 -3
  13. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +7 -1
  14. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +22 -3
  15. django_cfg/apps/integrations/grpc/services/__init__.py +102 -17
  16. django_cfg/apps/integrations/grpc/services/centrifugo/bridge.py +469 -0
  17. django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/demo.py +1 -1
  18. django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/test_publish.py +4 -4
  19. django_cfg/apps/integrations/grpc/services/client/__init__.py +26 -0
  20. django_cfg/apps/integrations/grpc/services/commands/IMPLEMENTATION.md +456 -0
  21. django_cfg/apps/integrations/grpc/services/commands/README.md +252 -0
  22. django_cfg/apps/integrations/grpc/services/commands/__init__.py +93 -0
  23. django_cfg/apps/integrations/grpc/services/commands/base.py +243 -0
  24. django_cfg/apps/integrations/grpc/services/commands/examples/__init__.py +22 -0
  25. django_cfg/apps/integrations/grpc/services/commands/examples/base_client.py +228 -0
  26. django_cfg/apps/integrations/grpc/services/commands/examples/client.py +272 -0
  27. django_cfg/apps/integrations/grpc/services/commands/examples/config.py +177 -0
  28. django_cfg/apps/integrations/grpc/services/commands/examples/start.py +125 -0
  29. django_cfg/apps/integrations/grpc/services/commands/examples/stop.py +101 -0
  30. django_cfg/apps/integrations/grpc/services/commands/registry.py +170 -0
  31. django_cfg/apps/integrations/grpc/services/discovery/__init__.py +39 -0
  32. django_cfg/apps/integrations/grpc/services/{discovery.py → discovery/discovery.py} +62 -55
  33. django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +216 -5
  34. django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/metrics.py +3 -3
  35. django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/request_logger.py +10 -13
  36. django_cfg/apps/integrations/grpc/services/management/__init__.py +37 -0
  37. django_cfg/apps/integrations/grpc/services/monitoring/__init__.py +38 -0
  38. django_cfg/apps/integrations/grpc/services/{monitoring_service.py → monitoring/monitoring.py} +2 -2
  39. django_cfg/apps/integrations/grpc/services/{testing_service.py → monitoring/testing.py} +5 -5
  40. django_cfg/apps/integrations/grpc/services/rendering/__init__.py +27 -0
  41. django_cfg/apps/integrations/grpc/services/{chart_generator.py → rendering/charts.py} +1 -1
  42. django_cfg/apps/integrations/grpc/services/routing/__init__.py +59 -0
  43. django_cfg/apps/integrations/grpc/services/routing/config.py +76 -0
  44. django_cfg/apps/integrations/grpc/services/routing/router.py +430 -0
  45. django_cfg/apps/integrations/grpc/services/streaming/__init__.py +117 -0
  46. django_cfg/apps/integrations/grpc/services/streaming/config.py +451 -0
  47. django_cfg/apps/integrations/grpc/services/streaming/service.py +651 -0
  48. django_cfg/apps/integrations/grpc/services/streaming/types.py +367 -0
  49. django_cfg/apps/integrations/grpc/utils/__init__.py +58 -1
  50. django_cfg/apps/integrations/grpc/utils/converters.py +565 -0
  51. django_cfg/apps/integrations/grpc/utils/handlers.py +242 -0
  52. django_cfg/apps/integrations/grpc/utils/proto_gen.py +1 -1
  53. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +55 -8
  54. django_cfg/apps/integrations/grpc/views/charts.py +1 -1
  55. django_cfg/apps/integrations/grpc/views/config.py +1 -1
  56. django_cfg/core/base/config_model.py +11 -0
  57. django_cfg/core/builders/middleware_builder.py +5 -0
  58. django_cfg/management/commands/pool_status.py +153 -0
  59. django_cfg/middleware/pool_cleanup.py +261 -0
  60. django_cfg/models/api/grpc/config.py +2 -2
  61. django_cfg/models/infrastructure/database/config.py +16 -0
  62. django_cfg/models/infrastructure/database/converters.py +2 -0
  63. django_cfg/modules/django_admin/utils/html/composition.py +57 -13
  64. django_cfg/modules/django_admin/utils/html_builder.py +1 -0
  65. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +12 -0
  66. django_cfg/modules/django_client/core/generator/typescript/generator.py +8 -0
  67. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +22 -0
  68. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +4 -0
  69. django_cfg/modules/django_client/core/generator/typescript/templates/utils/validation-events.ts.jinja +133 -0
  70. django_cfg/modules/django_client/core/groups/manager.py +25 -18
  71. django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
  72. django_cfg/modules/django_client/urls.py +38 -5
  73. django_cfg/modules/django_logging/django_logger.py +58 -19
  74. django_cfg/modules/django_twilio/email_otp.py +3 -1
  75. django_cfg/modules/django_twilio/sms.py +3 -1
  76. django_cfg/modules/django_twilio/unified.py +6 -2
  77. django_cfg/modules/django_twilio/whatsapp.py +3 -1
  78. django_cfg/pyproject.toml +3 -3
  79. django_cfg/static/frontend/admin.zip +0 -0
  80. django_cfg/templates/admin/index.html +17 -57
  81. django_cfg/utils/pool_monitor.py +320 -0
  82. django_cfg/utils/smart_defaults.py +233 -7
  83. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/METADATA +75 -5
  84. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/RECORD +97 -68
  85. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +0 -277
  86. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/__init__.py +0 -0
  87. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/config.py +0 -0
  88. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/transformers.py +0 -0
  89. /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
  90. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +0 -0
  91. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/centrifugo.py +0 -0
  92. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/errors.py +0 -0
  93. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/logging.py +0 -0
  94. /django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py} +0 -0
  95. /django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py} +0 -0
  96. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/WHEEL +0 -0
  97. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/entry_points.txt +0 -0
  98. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/licenses/LICENSE +0 -0
@@ -230,25 +230,32 @@ from django_cfg.apps.urls import urlpatterns
230
230
  for app_name in apps:
231
231
  # Try to include app URLs
232
232
  try:
233
- # Find app config by full name
234
- app_config = None
235
- for config in django_apps.get_app_configs():
236
- if config.name == app_name:
237
- app_config = config
238
- break
239
-
240
- if app_config:
241
- # Use actual label from AppConfig
242
- app_label = app_config.label
243
- # Add API prefix from config (e.g., "api/workspaces/" instead of just "workspaces/")
244
- api_prefix = getattr(self.config, 'api_prefix', '').strip('/')
245
- if api_prefix:
246
- url_path = f"{api_prefix}/{app_label}/"
247
- else:
248
- url_path = f"{app_label}/"
249
- urlpatterns.append(f' path("{url_path}", include("{app_name}.urls")),')
233
+ # Check if app has urls.py module
234
+ import importlib
235
+ urls_module = f"{app_name}.urls"
236
+ try:
237
+ importlib.import_module(urls_module)
238
+ has_urls = True
239
+ except ImportError:
240
+ has_urls = False
241
+
242
+ if not has_urls:
243
+ logger.debug(f"App '{app_name}' has no urls.py - skipping")
244
+ continue
245
+
246
+ # Determine URL path based on whether app has urls.py
247
+ # If app has urls.py, use basename (matches url_integration.py logic)
248
+ # e.g., "apps.web.controls" -> "controls"
249
+ app_basename = app_name.split('.')[-1]
250
+
251
+ # Add API prefix from config (e.g., "api/controls/" instead of just "controls/")
252
+ api_prefix = getattr(self.config, 'api_prefix', '').strip('/')
253
+ if api_prefix:
254
+ url_path = f"{api_prefix}/{app_basename}/"
250
255
  else:
251
- logger.debug(f"App '{app_name}' not found in installed apps")
256
+ url_path = f"{app_basename}/"
257
+
258
+ urlpatterns.append(f' path("{url_path}", include("{app_name}.urls")),')
252
259
  except Exception as e:
253
260
  logger.debug(f"App '{app_name}' skipped: {e}")
254
261
  continue
@@ -586,6 +586,14 @@ class Command(AdminCommand):
586
586
 
587
587
  self.stdout.write(f"\n📦 Copying TypeScript clients to Next.js admin...")
588
588
 
589
+ # Clean api_output_path before copying (remove old generated files)
590
+ if api_output_path.exists():
591
+ self.stdout.write(f" 🧹 Cleaning API output directory: {api_output_path.relative_to(project_path)}")
592
+ shutil.rmtree(api_output_path)
593
+
594
+ # Recreate directory
595
+ api_output_path.mkdir(parents=True, exist_ok=True)
596
+
589
597
  # Copy each group (exclude 'cfg' for Next.js admin)
590
598
  copied_count = 0
591
599
  for group_dir in ts_source.iterdir():
@@ -601,11 +609,7 @@ class Command(AdminCommand):
601
609
 
602
610
  target_dir = api_output_path / group_name
603
611
 
604
- # Remove old
605
- if target_dir.exists():
606
- shutil.rmtree(target_dir)
607
-
608
- # Copy new
612
+ # Copy group directory
609
613
  shutil.copytree(group_dir, target_dir)
610
614
  copied_count += 1
611
615
 
@@ -63,11 +63,44 @@ def get_openapi_urls() -> List[Any]:
63
63
 
64
64
 
65
65
  # Export urlpatterns for django.urls.include()
66
- # Only create urlpatterns if Django is configured
67
- if _is_django_configured():
68
- urlpatterns = get_openapi_urls()
69
- else:
70
- urlpatterns = []
66
+ # CRITICAL: Use lazy evaluation to avoid importing DRF/drf-spectacular
67
+ # before Django settings are fully loaded. This prevents api_settings
68
+ # from being cached with wrong DEFAULT_SCHEMA_CLASS value.
69
+ class LazyURLPatterns:
70
+ """Lazy URLpatterns that only initialize when accessed."""
71
+
72
+ def __init__(self):
73
+ self._patterns = None
74
+
75
+ def _get_patterns(self):
76
+ if self._patterns is None:
77
+ if _is_django_configured():
78
+ self._patterns = get_openapi_urls()
79
+ else:
80
+ self._patterns = []
81
+ return self._patterns
82
+
83
+ def __iter__(self):
84
+ return iter(self._get_patterns())
85
+
86
+ def __getitem__(self, index):
87
+ return self._get_patterns()[index]
88
+
89
+ def __len__(self):
90
+ return len(self._get_patterns())
91
+
92
+ def clear(self):
93
+ """Clear all patterns."""
94
+ patterns = self._get_patterns()
95
+ patterns.clear()
96
+
97
+ def extend(self, items):
98
+ """Extend patterns with new items."""
99
+ patterns = self._get_patterns()
100
+ patterns.extend(items)
101
+
102
+
103
+ urlpatterns = LazyURLPatterns()
71
104
 
72
105
 
73
106
  __all__ = ["get_openapi_urls", "urlpatterns"]
@@ -65,6 +65,32 @@ class DjangoLogger(BaseCfgModule):
65
65
 
66
66
  _loggers: Dict[str, logging.Logger] = {}
67
67
  _configured = False
68
+ _debug_mode: Optional[bool] = None # Cached debug mode to avoid repeated config loads
69
+
70
+ @classmethod
71
+ def _get_debug_mode(cls) -> bool:
72
+ """
73
+ Get debug mode from config (cached).
74
+
75
+ Loads config only once and caches the result to avoid repeated config loads.
76
+ This is a performance optimization - config loading can be expensive.
77
+
78
+ Returns:
79
+ True if debug mode is enabled, False otherwise
80
+ """
81
+ if cls._debug_mode is not None:
82
+ return cls._debug_mode
83
+
84
+ # Load config once and cache
85
+ try:
86
+ from django_cfg.core.state import get_current_config
87
+ config = get_current_config()
88
+ cls._debug_mode = config.debug if config and hasattr(config, 'debug') else False
89
+ except Exception:
90
+ import os
91
+ cls._debug_mode = os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
92
+
93
+ return cls._debug_mode
68
94
 
69
95
  @classmethod
70
96
  def get_logger(cls, name: str = "django_cfg") -> logging.Logger:
@@ -92,13 +118,8 @@ class DjangoLogger(BaseCfgModule):
92
118
  # print(f" Django logs: {logs_dir / 'django.log'}")
93
119
  # print(f" Django-CFG logs: {djangocfg_logs_dir}/")
94
120
 
95
- # Get debug mode
96
- try:
97
- from django_cfg.core.state import get_current_config
98
- config = get_current_config()
99
- debug = config.debug if config else False
100
- except Exception:
101
- debug = os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
121
+ # Get debug mode (cached - loaded once)
122
+ debug = cls._get_debug_mode()
102
123
 
103
124
  # Create handlers
104
125
  try:
@@ -111,9 +132,13 @@ class DjangoLogger(BaseCfgModule):
111
132
  backupCount=30, # Keep 30 days of logs
112
133
  encoding='utf-8',
113
134
  )
114
- django_handler.setLevel(logging.DEBUG if debug else logging.INFO) # INFO+ in production, DEBUG in dev
135
+ # File handlers ALWAYS capture DEBUG in dev mode (for complete debugging history)
136
+ # In production, still use INFO+ to save disk space
137
+ django_handler.setLevel(logging.DEBUG if debug else logging.INFO)
115
138
 
116
- # Console handler - WARNING+ always (less noise)
139
+ # Console handler - configurable noise level
140
+ # In dev: show DEBUG+ (full visibility)
141
+ # In production: show WARNING+ only (reduce noise)
117
142
  console_handler = logging.StreamHandler()
118
143
  console_handler.setLevel(logging.DEBUG if debug else logging.WARNING)
119
144
 
@@ -123,8 +148,10 @@ class DjangoLogger(BaseCfgModule):
123
148
  console_handler.setFormatter(formatter)
124
149
 
125
150
  # Configure root logger
151
+ # CRITICAL: Root logger must be DEBUG in dev mode to allow all messages through
152
+ # Handlers will filter based on their own levels, but logger must not block
126
153
  root_logger = logging.getLogger()
127
- root_logger.setLevel(logging.DEBUG if debug else logging.INFO) # INFO+ in production, DEBUG in dev
154
+ root_logger.setLevel(logging.DEBUG if debug else logging.INFO)
128
155
 
129
156
  # Clear existing handlers
130
157
  root_logger.handlers.clear()
@@ -149,9 +176,24 @@ class DjangoLogger(BaseCfgModule):
149
176
 
150
177
  @classmethod
151
178
  def _create_logger(cls, name: str) -> logging.Logger:
152
- """Create logger with modular file handling for django-cfg loggers."""
179
+ """
180
+ Create logger with modular file handling for django-cfg loggers.
181
+
182
+ In dev/debug mode, loggers inherit DEBUG level from root logger,
183
+ ensuring all log messages reach file handlers regardless of explicit level settings.
184
+ """
153
185
  logger = logging.getLogger(name)
154
186
 
187
+ # In dev mode, ensure logger doesn't block DEBUG messages
188
+ # Logger inherits from root by default (propagate=True), which is set to DEBUG in dev
189
+ # This is crucial: logger level must be <= handler level, or messages get blocked
190
+ debug = cls._get_debug_mode() # Use cached debug mode
191
+
192
+ # In dev mode, force DEBUG level on logger to ensure complete file logging
193
+ # Handlers will still filter console output (WARNING+), but files get everything (DEBUG+)
194
+ if debug and not logger.level:
195
+ logger.setLevel(logging.DEBUG)
196
+
155
197
  # If this is a django-cfg logger, add a specific file handler
156
198
  if name.startswith('django_cfg'):
157
199
  try:
@@ -181,15 +223,12 @@ class DjangoLogger(BaseCfgModule):
181
223
  encoding='utf-8',
182
224
  )
183
225
 
184
- # Get debug mode
185
- try:
186
- from django_cfg.core.state import get_current_config
187
- config = get_current_config()
188
- debug = config.debug if config else False
189
- except Exception:
190
- debug = os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
226
+ # Get debug mode (cached - loaded once)
227
+ debug = cls._get_debug_mode()
191
228
 
192
- file_handler.setLevel(logging.DEBUG if debug else logging.INFO) # INFO+ in production, DEBUG in dev
229
+ # Module file handlers ALWAYS capture DEBUG in dev mode
230
+ # This ensures complete log history for debugging, independent of logger level
231
+ file_handler.setLevel(logging.DEBUG if debug else logging.INFO)
193
232
 
194
233
  # Set format
195
234
  formatter = logging.Formatter('[%(asctime)s] %(levelname)s in %(name)s [%(filename)s:%(lineno)d]: %(message)s')
@@ -111,7 +111,9 @@ class EmailOTPService(BaseTwilioService):
111
111
  template_data: Optional[Dict[str, Any]] = None
112
112
  ) -> Tuple[bool, str, str]:
113
113
  """Async version of send_otp."""
114
- return await sync_to_async(self.send_otp)(email, subject, template_data)
114
+ # sync_to_async is appropriate here for external SendGrid API calls (not Django ORM)
115
+ # thread_sensitive=False for better performance since no database access occurs
116
+ return await sync_to_async(self.send_otp, thread_sensitive=False)(email, subject, template_data)
115
117
 
116
118
  def _send_template_email(
117
119
  self,
@@ -87,7 +87,9 @@ class SMSOTPService(BaseTwilioService):
87
87
 
88
88
  async def asend_otp(self, phone_number: str) -> Tuple[bool, str]:
89
89
  """Async version of send_otp."""
90
- return await sync_to_async(self.send_otp)(phone_number)
90
+ # sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
91
+ # thread_sensitive=False for better performance since no database access occurs
92
+ return await sync_to_async(self.send_otp, thread_sensitive=False)(phone_number)
91
93
 
92
94
 
93
95
  __all__ = [
@@ -105,7 +105,9 @@ class UnifiedOTPService(BaseTwilioService):
105
105
  enable_fallback: bool = True
106
106
  ) -> Tuple[bool, str, TwilioChannelType]:
107
107
  """Async version of send_otp."""
108
- return await sync_to_async(self.send_otp)(identifier, preferred_channel, enable_fallback)
108
+ # sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
109
+ # thread_sensitive=False for better performance since no database access occurs
110
+ return await sync_to_async(self.send_otp, thread_sensitive=False)(identifier, preferred_channel, enable_fallback)
109
111
 
110
112
  def verify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
111
113
  """
@@ -129,7 +131,9 @@ class UnifiedOTPService(BaseTwilioService):
129
131
 
130
132
  async def averify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
131
133
  """Async version of verify_otp."""
132
- return await sync_to_async(self.verify_otp)(identifier, code)
134
+ # sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
135
+ # thread_sensitive=False for better performance since no database access occurs
136
+ return await sync_to_async(self.verify_otp, thread_sensitive=False)(identifier, code)
133
137
 
134
138
  def _get_available_channels(self, is_email: bool, config: TwilioConfig) -> List[TwilioChannelType]:
135
139
  """Get list of available channels based on configuration."""
@@ -101,7 +101,9 @@ class WhatsAppOTPService(BaseTwilioService):
101
101
 
102
102
  async def asend_otp(self, phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
103
103
  """Async version of send_otp."""
104
- return await sync_to_async(self.send_otp)(phone_number, fallback_to_sms)
104
+ # sync_to_async is appropriate here for external Twilio API calls (not Django ORM)
105
+ # thread_sensitive=False for better performance since no database access occurs
106
+ return await sync_to_async(self.send_otp, thread_sensitive=False)(phone_number, fallback_to_sms)
105
107
 
106
108
  def _send_sms_otp(self, phone_number: str, client: Client, verify_config: TwilioVerifyConfig) -> Tuple[bool, str]:
107
109
  """Internal SMS fallback method."""
django_cfg/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "django-cfg"
7
- version = "1.5.20"
7
+ version = "1.5.31"
8
8
  description = "Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features."
9
9
  readme = "README.md"
10
10
  keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "nextjs-admin", "react-admin", "websocket", "centrifugo", "real-time", "typescript-generation", "ai-agents", "enterprise-django", "django-settings", "type-safe-config", "modern-django",]
@@ -26,13 +26,13 @@ text = "MIT"
26
26
  local = []
27
27
  django52 = [ "django>=5.2,<6.0",]
28
28
  ai = [ "pydantic-ai>=1.0.10,<2.0",]
29
- grpc = [ "grpcio>=1.50.0,<2.0", "grpcio-tools>=1.50.0,<2.0", "grpcio-reflection>=1.50.0,<2.0", "grpcio-health-checking>=1.50.0,<2.0", "protobuf>=5.0,<7.0",]
29
+ grpc = [ "grpcio>=1.50.0,<2.0", "grpcio-tools>=1.50.0,<2.0", "grpcio-reflection>=1.50.0,<2.0", "grpcio-health-checking>=1.50.0,<2.0", "protobuf>=5.0,<7.0", "aiobreaker>=1.2.0,<2.0",]
30
30
  centrifugo = [ "cent>=5.0.0,<6.0", "websockets>=13.0,<15.0",]
31
31
  dev = [ "django>=5.2,<6.0", "pytest>=7.0", "pytest-django>=4.5", "pytest-cov>=4.0", "pytest-mock>=3.0", "factory-boy>=3.0", "fakeredis>=2.0", "black>=23.0", "isort>=5.0", "flake8>=5.0", "mypy>=1.0", "pre-commit>=3.0", "build>=1.0", "twine>=4.0", "tomlkit>=0.11", "questionary>=2.0", "rich>=13.0", "mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24", "redis>=5.0",]
32
32
  test = [ "django>=5.2,<6.0", "pytest>=7.0", "pytest-django>=4.5", "pytest-cov>=4.0", "pytest-mock>=3.0", "pytest-xdist>=3.0", "factory-boy>=3.0", "fakeredis>=2.0",]
33
33
  docs = [ "mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24", "pymdown-extensions>=10.0",]
34
34
  tasks = [ "redis>=5.0",]
35
- full = [ "django>=5.2,<6.0", "pytest>=7.0", "pytest-django>=4.5", "pytest-cov>=4.0", "pytest-mock>=3.0", "pytest-xdist>=3.0", "factory-boy>=3.0", "black>=23.0", "isort>=5.0", "flake8>=5.0", "mypy>=1.0", "pre-commit>=3.0", "build>=1.0", "twine>=4.0", "tomlkit>=0.11", "questionary>=2.0", "rich>=13.0", "mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24", "pymdown-extensions>=10.0", "redis>=5.0", "grpcio>=1.50.0,<2.0", "grpcio-tools>=1.50.0,<2.0", "grpcio-reflection>=1.50.0,<2.0", "grpcio-health-checking>=1.50.0,<2.0", "protobuf>=5.0,<7.0", "cent>=5.0.0,<6.0", "websockets>=13.0,<15.0", "django-rq>=3.0", "rq>=1.0", "rq-scheduler>=0.13", "hiredis>=2.0",]
35
+ full = [ "django>=5.2,<6.0", "pytest>=7.0", "pytest-django>=4.5", "pytest-cov>=4.0", "pytest-mock>=3.0", "pytest-xdist>=3.0", "factory-boy>=3.0", "black>=23.0", "isort>=5.0", "flake8>=5.0", "mypy>=1.0", "pre-commit>=3.0", "build>=1.0", "twine>=4.0", "tomlkit>=0.11", "questionary>=2.0", "rich>=13.0", "mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24", "pymdown-extensions>=10.0", "redis>=5.0", "grpcio>=1.50.0,<2.0", "grpcio-tools>=1.50.0,<2.0", "grpcio-reflection>=1.50.0,<2.0", "grpcio-health-checking>=1.50.0,<2.0", "protobuf>=5.0,<7.0", "aiobreaker>=1.2.0,<2.0", "cent>=5.0.0,<6.0", "websockets>=13.0,<15.0", "django-rq>=3.0", "rq>=1.0", "rq-scheduler>=0.13", "hiredis>=2.0",]
36
36
  rq = [ "django-rq>=3.0", "rq>=1.0", "rq-scheduler>=0.13", "redis>=5.0", "hiredis>=2.0",]
37
37
 
38
38
  [project.urls]
Binary file
@@ -179,8 +179,6 @@
179
179
  iframeId = 'nextjs-dashboard-iframe-builtin';
180
180
  } else if (tab === 'nextjs') {
181
181
  iframeId = 'nextjs-dashboard-iframe-nextjs';
182
- } else if (tab === 'docs') {
183
- iframeId = 'nextjs-dashboard-iframe-docs';
184
182
  }
185
183
 
186
184
  const iframe = document.getElementById(iframeId);
@@ -200,8 +198,6 @@
200
198
  iframeId = 'nextjs-dashboard-iframe-builtin';
201
199
  } else if (this.activeTab === 'nextjs') {
202
200
  iframeId = 'nextjs-dashboard-iframe-nextjs';
203
- } else if (this.activeTab === 'docs') {
204
- iframeId = 'nextjs-dashboard-iframe-docs';
205
201
  }
206
202
 
207
203
  const iframe = document.getElementById(iframeId);
@@ -298,17 +294,6 @@
298
294
  <span class="hidden sm:inline">{% nextjs_external_admin_title %}</span>
299
295
  <span class="sm:hidden">Admin</span>
300
296
  </button>
301
-
302
- {% if is_development %}
303
- <button @click="switchTab('docs')"
304
- class="whitespace-nowrap py-4 px-2 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-200"
305
- :class="activeTab === 'docs'
306
- ? 'border-primary-600 text-primary-600 dark:border-primary-500 dark:text-primary-500'
307
- : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 dark:hover:border-gray-700'">
308
- <span class="material-icons text-base">description</span>
309
- <span>Docs</span>
310
- </button>
311
- {% endif %}
312
297
  </nav>
313
298
 
314
299
  <!-- Actions & Version -->
@@ -372,27 +357,6 @@
372
357
  ></iframe>
373
358
  </div>
374
359
  </div>
375
-
376
- <!-- Docs Tab Content (Development Mode Only) -->
377
- {% if is_development %}
378
- <div x-show="activeTab === 'docs'" style="display: none;">
379
- <div class="iframe-container">
380
- <div class="iframe-loading" id="iframe-loading-docs">
381
- <div class="spinner"></div>
382
- <p id="loading-text-docs">Loading documentation...</p>
383
- </div>
384
-
385
- <iframe
386
- id="nextjs-dashboard-iframe-docs"
387
- class="nextjs-dashboard-iframe"
388
- src="{% lib_docs_url %}"
389
- data-original-src="{% lib_docs_url %}"
390
- title="Documentation"
391
- sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation"
392
- ></iframe>
393
- </div>
394
- </div>
395
- {% endif %}
396
360
  </div>
397
361
  {% endcomponent %}
398
362
  {% endblock %}
@@ -460,7 +424,7 @@
460
424
  const authToken = localStorage.getItem('auth_token');
461
425
  const refreshToken = localStorage.getItem('refresh_token');
462
426
 
463
- // console.log('[Django-CFG] sendAuth: authToken:', authToken ? authToken.substring(0, 20) + '...' : 'null', 'refreshToken:', refreshToken ? refreshToken.substring(0, 20) + '...' : 'null');
427
+ console.log('[Django-CFG] sendAuth: authToken:', authToken ? authToken.substring(0, 20) + '...' : 'null', 'refreshToken:', refreshToken ? refreshToken.substring(0, 20) + '...' : 'null');
464
428
 
465
429
  if (!authToken) {
466
430
  console.warn('[Django-CFG] sendAuth: No auth token found in localStorage');
@@ -470,12 +434,12 @@
470
434
  try {
471
435
  // Use '*' in dev mode to handle localhost vs 127.0.0.1 mismatch
472
436
  const targetOrigin = '*'; // In production, use specific origin for security
473
- // console.log('[Django-CFG] Sending parent-auth message to iframe:', this.iframe.id);
437
+ console.log('[Django-CFG] Sending parent-auth message to iframe:', this.iframe.id);
474
438
  this.iframe.contentWindow.postMessage({
475
439
  type: 'parent-auth',
476
440
  data: { authToken, refreshToken }
477
441
  }, targetOrigin);
478
- // console.log('[Django-CFG] parent-auth message sent successfully');
442
+ console.log('[Django-CFG] parent-auth message sent successfully');
479
443
  } catch (e) {
480
444
  console.error('[Django-CFG] Failed to send auth:', e);
481
445
  }
@@ -485,10 +449,10 @@
485
449
  * Send all data (theme + auth)
486
450
  */
487
451
  sendAllData() {
488
- // console.log('[Django-CFG] sendAllData called');
452
+ console.log('[Django-CFG] sendAllData called');
489
453
  this.sendTheme();
490
454
  this.sendAuth();
491
- // console.log('[Django-CFG] sendAllData completed');
455
+ console.log('[Django-CFG] sendAllData completed');
492
456
  }
493
457
 
494
458
  /**
@@ -562,15 +526,15 @@
562
526
  }
563
527
 
564
528
  handleMessage(type, data) {
565
- // console.log('[Django-CFG] Received message from iframe:', type, data);
529
+ console.log('[Django-CFG] Received message from iframe:', type, data);
566
530
  switch (type) {
567
531
  case 'iframe-ready':
568
- // console.log('[Django-CFG] iframe-ready received, sending auth and theme data');
532
+ console.log('[Django-CFG] iframe-ready received, sending auth and theme data');
569
533
  this.messageBridge.sendAllData();
570
534
  break;
571
535
 
572
536
  case 'iframe-auth-status':
573
- // console.log('[Django-CFG] iframe-auth-status:', data);
537
+ console.log('[Django-CFG] iframe-auth-status:', data);
574
538
  break;
575
539
 
576
540
  case 'iframe-navigation':
@@ -676,26 +640,25 @@
676
640
  * Setup ONE global message listener for ALL iframes
677
641
  */
678
642
  _setupGlobalMessageListener() {
679
- // console.log('[Django-CFG] Setting up global message listener');
643
+ console.log('[Django-CFG] Setting up global message listener');
680
644
  window.addEventListener('message', (event) => {
681
- // console.log('[Django-CFG] Received message:', {
682
- // type: event.data?.type,
683
- // origin: event.origin,
684
- // source: event.source,
685
- // mapSize: this.iframeMap.size,
686
- // mapKeys: Array.from(this.iframeMap.keys())
687
- // });
645
+ console.log('[Django-CFG] Received message:', {
646
+ type: event.data?.type,
647
+ origin: event.origin,
648
+ source: event.source === window ? 'self' : 'iframe',
649
+ mapSize: this.iframeMap.size,
650
+ });
688
651
 
689
652
  // Find which handler should handle this message
690
653
  const handler = this.iframeMap.get(event.source);
691
654
 
692
655
  if (handler) {
693
- // console.log('[Django-CFG] Routing message to handler:', handler.iframe.id);
656
+ console.log('[Django-CFG] Routing message to handler:', handler.iframe.id);
694
657
  const { type, data } = event.data || {};
695
658
  handler.handleMessage(type, data);
696
659
  } else {
697
660
  // Message from unknown source (browser extension, etc.)
698
- // console.log('[Django-CFG] Ignoring message from unknown source');
661
+ console.log('[Django-CFG] Ignoring message from unknown source, origin:', event.origin);
699
662
  }
700
663
  });
701
664
  }
@@ -807,9 +770,6 @@
807
770
 
808
771
  manager.register('nextjs-dashboard-iframe-builtin', 'iframe-loading-builtin');
809
772
  manager.register('nextjs-dashboard-iframe-nextjs', 'iframe-loading-nextjs');
810
- {% if is_development %}
811
- manager.register('nextjs-dashboard-iframe-docs', 'iframe-loading-docs', { external: true });
812
- {% endif %}
813
773
  // console.log('[Django-CFG] Iframe registration completed');
814
774
  }
815
775