django-cfg 1.5.14__py3-none-any.whl → 1.5.20__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 (53) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/business/accounts/serializers/profile.py +42 -0
  3. django_cfg/apps/business/support/serializers.py +3 -2
  4. django_cfg/apps/integrations/centrifugo/apps.py +2 -1
  5. django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
  6. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +2 -2
  7. django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
  8. django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
  9. django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
  10. django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
  11. django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
  12. django_cfg/apps/integrations/centrifugo/urls.py +8 -0
  13. django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
  14. django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
  15. django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
  16. django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
  17. django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
  18. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
  19. django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
  20. django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
  21. django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
  22. django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
  23. django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
  24. django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
  25. django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
  26. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +55 -0
  27. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +311 -7
  28. django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
  29. django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
  30. django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
  31. django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
  32. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +206 -5
  33. django_cfg/apps/system/dashboard/serializers/config.py +95 -9
  34. django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
  35. django_cfg/apps/system/frontend/views.py +87 -6
  36. django_cfg/core/builders/security_builder.py +1 -0
  37. django_cfg/core/generation/integration_generators/api.py +2 -0
  38. django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
  39. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
  40. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
  41. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
  42. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
  43. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
  44. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
  45. django_cfg/modules/django_client/core/ir/schema.py +15 -1
  46. django_cfg/modules/django_client/core/parser/base.py +12 -0
  47. django_cfg/pyproject.toml +1 -1
  48. django_cfg/static/frontend/admin.zip +0 -0
  49. {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
  50. {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/RECORD +53 -37
  51. {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
  52. {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
  53. {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
@@ -53,6 +53,10 @@ class ServiceDiscovery:
53
53
  # Get config from django-cfg using Pydantic2 pattern
54
54
  self.config = get_grpc_config()
55
55
 
56
+ logger.warning(f"🔍 ServiceDiscovery.__init__: config = {self.config}")
57
+ if self.config:
58
+ logger.warning(f"🔍 handlers_hook = {self.config.handlers_hook}")
59
+
56
60
  if self.config:
57
61
  self.auto_register = self.config.auto_register_apps
58
62
  self.enabled_apps = self.config.enabled_apps if self.config.auto_register_apps else []
@@ -452,11 +456,13 @@ class ServiceDiscovery:
452
456
  >>> if handlers_hook:
453
457
  ... services = handlers_hook(server)
454
458
  """
459
+ logger.warning(f"🔍 get_handlers_hook called")
455
460
  if not self.config:
456
- logger.debug("No gRPC config available")
461
+ logger.warning("No gRPC config available")
457
462
  return None
458
463
 
459
464
  handlers_hook_path = self.config.handlers_hook
465
+ logger.warning(f"🔍 handlers_hook_path = '{handlers_hook_path}'")
460
466
 
461
467
  if not handlers_hook_path:
462
468
  logger.debug("No handlers_hook configured")
@@ -0,0 +1,164 @@
1
+ # Server Lifecycle Logging Utilities
2
+
3
+ Reusable functions for logging server startup and shutdown with timestamps and uptime tracking.
4
+
5
+ **Uses Rich** for beautiful output with panels, tables, and colors! 🎨
6
+
7
+ ## 📦 Functions
8
+
9
+ ### `log_server_start()`
10
+
11
+ Logs server startup with timestamp and configuration.
12
+
13
+ ```python
14
+ from django_cfg.apps.integrations.grpc.utils import log_server_start
15
+
16
+ start_time = log_server_start(
17
+ logger,
18
+ server_type="gRPC Server", # Server type
19
+ mode="Development", # Production/Development
20
+ hotreload_enabled=True, # Is hotreload enabled?
21
+ host="0.0.0.0", # Additional parameters
22
+ port=50051
23
+ )
24
+ ```
25
+
26
+ **Output (Rich Panel):**
27
+ ```
28
+ ╭────────────── 🚀 gRPC Server Starting ───────────────╮
29
+ │ │
30
+ │ ⏰ Started at 2025-11-05 14:30:15 │
31
+ │ Mode Development │
32
+ │ Hotreload Enabled ⚡ │
33
+ │ Host 0.0.0.0 │
34
+ │ Port 50051 │
35
+ │ │
36
+ ╰──────────────────────────────────────────────────────╯
37
+ ⚠️ Hotreload active - connections may be dropped on code changes
38
+ ```
39
+ (with green border and colors!)
40
+
41
+ **Returns:** `datetime` object of start time (for use in `log_server_shutdown`)
42
+
43
+ ---
44
+
45
+ ### `log_server_shutdown()`
46
+
47
+ Logs server shutdown with uptime calculation.
48
+
49
+ ```python
50
+ from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
51
+
52
+ log_server_shutdown(
53
+ logger,
54
+ start_time, # datetime from log_server_start()
55
+ server_type="gRPC Server",
56
+ reason="Hotreload triggered", # Shutdown reason
57
+ active_connections=5 # Additional parameters
58
+ )
59
+ ```
60
+
61
+ **Output (Rich Panel):**
62
+ ```
63
+ ╭───────────── 🧹 Shutting down gRPC Server ──────────────╮
64
+ │ │
65
+ │ 📋 Reason Hotreload triggered │
66
+ │ ⏱️ Uptime 0h 2m 35s │
67
+ │ 🕐 Stopped at 2025-11-05 14:32:50 │
68
+ │ Active Connections 5 │
69
+ │ │
70
+ ╰──────────────────────────────────────────────────────────╯
71
+ ✅ Server shutdown complete
72
+ ```
73
+ (with red border and colors!)
74
+
75
+ ---
76
+
77
+ ## 🎯 Complete Usage Example
78
+
79
+ ```python
80
+ from django_cfg.apps.integrations.grpc.utils import (
81
+ setup_streaming_logger,
82
+ log_server_start,
83
+ log_server_shutdown,
84
+ )
85
+
86
+ # Create logger
87
+ logger = setup_streaming_logger('my_server')
88
+
89
+ # Log server start
90
+ start_time = log_server_start(
91
+ logger,
92
+ server_type="WebSocket Server",
93
+ mode="Production",
94
+ hotreload_enabled=False,
95
+ host="0.0.0.0",
96
+ port=8000
97
+ )
98
+
99
+ try:
100
+ # Server work...
101
+ await server.serve_forever()
102
+ shutdown_reason = "Normal termination"
103
+ except KeyboardInterrupt:
104
+ shutdown_reason = "Keyboard interrupt"
105
+ finally:
106
+ # Log server shutdown
107
+ log_server_shutdown(
108
+ logger,
109
+ start_time,
110
+ server_type="WebSocket Server",
111
+ reason=shutdown_reason,
112
+ total_connections=1250
113
+ )
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 📊 Used in:
119
+
120
+ - ✅ `rungrpc` - gRPC server command
121
+ - ✅ Can be used for any servers (WebSocket, HTTP, etc.)
122
+
123
+ ---
124
+
125
+ ## 🔧 Parameters
126
+
127
+ ### `log_server_start()`
128
+
129
+ | Parameter | Type | Required | Description |
130
+ |----------|-----|----------|-------------|
131
+ | `logger` | `logging.Logger` | ✅ Yes | Logger instance |
132
+ | `server_type` | `str` | ❌ No | Server type (default: "Server") |
133
+ | `mode` | `str` | ❌ No | Running mode (default: "Development") |
134
+ | `hotreload_enabled` | `bool` | ❌ No | Is hotreload enabled (default: False) |
135
+ | `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
136
+ | `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
137
+
138
+ ### `log_server_shutdown()`
139
+
140
+ | Parameter | Type | Required | Description |
141
+ |----------|-----|----------|-------------|
142
+ | `logger` | `logging.Logger` | ✅ Yes | Logger instance |
143
+ | `start_time` | `datetime` | ✅ Yes | Start time from `log_server_start()` |
144
+ | `server_type` | `str` | ❌ No | Server type (default: "Server") |
145
+ | `reason` | `str` | ❌ No | Shutdown reason |
146
+ | `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
147
+ | `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
148
+
149
+ ---
150
+
151
+ ## 💡 Benefits
152
+
153
+ 1. ✅ **DRY** - single code for all servers
154
+ 2. ✅ **Consistency** - uniform log format
155
+ 3. ✅ **Rich UI** - beautiful panels, tables, colors 🎨
156
+ 4. ✅ **Uptime tracking** - automatic time calculation
157
+ 5. ✅ **Flexible** - can add arbitrary parameters via `**extra_info`
158
+ 6. ✅ **Hotreload aware** - special warning for hotreload mode
159
+ 7. ✅ **Fallback** - works without Rich (if `use_rich=False`)
160
+
161
+ ---
162
+
163
+ **Author:** django-cfg team
164
+ **Date:** 2025-11-05
@@ -12,6 +12,12 @@ from pathlib import Path
12
12
  from typing import Optional
13
13
  from django.utils import timezone
14
14
 
15
+ # Rich for beautiful console output
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+ from rich.table import Table
19
+ from rich.text import Text
20
+
15
21
 
16
22
  class AutoTracebackHandler(logging.Handler):
17
23
  """
@@ -128,11 +134,14 @@ def setup_streaming_logger(
128
134
  file_handler = AutoTracebackHandler(base_file_handler)
129
135
  streaming_logger.addHandler(file_handler)
130
136
 
131
- # Console handler - important messages only
132
- console_handler = logging.StreamHandler()
133
- console_handler.setLevel(console_level)
137
+ # Console handler - important messages only (also with auto-traceback)
138
+ base_console_handler = logging.StreamHandler()
139
+ base_console_handler.setLevel(console_level)
134
140
  console_formatter = logging.Formatter('%(levelname)s: %(message)s')
135
- console_handler.setFormatter(console_formatter)
141
+ base_console_handler.setFormatter(console_formatter)
142
+
143
+ # Wrap console handler with auto-traceback too
144
+ console_handler = AutoTracebackHandler(base_console_handler)
136
145
  streaming_logger.addHandler(console_handler)
137
146
 
138
147
  # Prevent propagation to avoid duplicate logs
@@ -174,4 +183,196 @@ def get_streaming_logger(name: str = "grpc_streaming") -> logging.Logger:
174
183
  return logger
175
184
 
176
185
 
177
- __all__ = ["setup_streaming_logger", "get_streaming_logger"]
186
+ def log_server_start(
187
+ logger: logging.Logger,
188
+ server_type: str = "Server",
189
+ mode: str = "Development",
190
+ hotreload_enabled: bool = False,
191
+ use_rich: bool = True,
192
+ **extra_info
193
+ ):
194
+ """
195
+ Log server startup with timestamp and configuration using Rich panels.
196
+
197
+ Args:
198
+ logger: Logger instance to use
199
+ server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
200
+ mode: Running mode (Development/Production)
201
+ hotreload_enabled: Whether hot-reload is enabled
202
+ use_rich: Use Rich for beautiful output (default: True)
203
+ **extra_info: Additional key-value pairs to log
204
+
205
+ Example:
206
+ ```python
207
+ from django_cfg.apps.integrations.grpc.utils import log_server_start
208
+ from datetime import datetime
209
+
210
+ start_time = log_server_start(
211
+ logger,
212
+ server_type="gRPC Server",
213
+ mode="Development",
214
+ hotreload_enabled=True,
215
+ host="0.0.0.0",
216
+ port=50051
217
+ )
218
+ ```
219
+
220
+ Returns:
221
+ datetime object of start time for later use in log_server_shutdown
222
+ """
223
+ from datetime import datetime
224
+
225
+ start_time = datetime.now()
226
+
227
+ if use_rich:
228
+ # Create Rich table for server info
229
+ console = Console()
230
+
231
+ table = Table(show_header=False, box=None, padding=(0, 1))
232
+ table.add_column("Key", style="cyan")
233
+ table.add_column("Value", style="white")
234
+
235
+ table.add_row("⏰ Started at", start_time.strftime('%Y-%m-%d %H:%M:%S'))
236
+ table.add_row("Mode", f"[{'red' if mode == 'Production' else 'green'}]{mode}[/]")
237
+ table.add_row("Hotreload", f"[{'yellow' if hotreload_enabled else 'dim'}]{'Enabled ⚡' if hotreload_enabled else 'Disabled'}[/]")
238
+
239
+ # Add extra info
240
+ for key, value in extra_info.items():
241
+ key_display = key.replace('_', ' ').title()
242
+ table.add_row(key_display, str(value))
243
+
244
+ # Create panel
245
+ panel = Panel(
246
+ table,
247
+ title=f"[bold green]🚀 {server_type} Starting[/bold green]",
248
+ border_style="green",
249
+ padding=(1, 2)
250
+ )
251
+
252
+ console.print(panel)
253
+
254
+ if hotreload_enabled:
255
+ console.print(
256
+ "[yellow]⚠️ Hotreload active - connections may be dropped on code changes[/yellow]",
257
+ style="bold"
258
+ )
259
+ else:
260
+ # Fallback to simple logging
261
+ logger.info("=" * 80)
262
+ logger.info(f"🚀 {server_type} Starting")
263
+ logger.info(f" ⏰ Started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
264
+ logger.info(f" Mode: {mode}")
265
+ logger.info(f" Hotreload: {'Enabled' if hotreload_enabled else 'Disabled'}")
266
+
267
+ for key, value in extra_info.items():
268
+ key_display = key.replace('_', ' ').title()
269
+ logger.info(f" {key_display}: {value}")
270
+
271
+ if hotreload_enabled:
272
+ logger.warning(
273
+ "⚠️ Hotreload active - connections may be dropped on code changes"
274
+ )
275
+
276
+ logger.info("=" * 80)
277
+
278
+ return start_time
279
+
280
+
281
+ def log_server_shutdown(
282
+ logger: logging.Logger,
283
+ start_time,
284
+ server_type: str = "Server",
285
+ reason: str = None,
286
+ use_rich: bool = True,
287
+ **extra_info
288
+ ):
289
+ """
290
+ Log server shutdown with uptime calculation using Rich panels.
291
+
292
+ Args:
293
+ logger: Logger instance to use
294
+ start_time: datetime object from log_server_start()
295
+ server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
296
+ reason: Shutdown reason (e.g., "Keyboard interrupt", "Hotreload")
297
+ use_rich: Use Rich for beautiful output (default: True)
298
+ **extra_info: Additional key-value pairs to log
299
+
300
+ Example:
301
+ ```python
302
+ from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
303
+
304
+ log_server_shutdown(
305
+ logger,
306
+ start_time,
307
+ server_type="gRPC Server",
308
+ reason="Hotreload triggered",
309
+ active_connections=5
310
+ )
311
+ ```
312
+ """
313
+ from datetime import datetime
314
+
315
+ end_time = datetime.now()
316
+ uptime = end_time - start_time
317
+ uptime_seconds = int(uptime.total_seconds())
318
+
319
+ # Format uptime
320
+ hours = uptime_seconds // 3600
321
+ minutes = (uptime_seconds % 3600) // 60
322
+ seconds = uptime_seconds % 60
323
+ uptime_str = f"{hours}h {minutes}m {seconds}s"
324
+
325
+ if use_rich:
326
+ # Create Rich table for shutdown info
327
+ console = Console()
328
+
329
+ table = Table(show_header=False, box=None, padding=(0, 1))
330
+ table.add_column("Key", style="cyan")
331
+ table.add_column("Value", style="white")
332
+
333
+ if reason:
334
+ table.add_row("📋 Reason", reason)
335
+
336
+ table.add_row("⏱️ Uptime", f"[bold]{uptime_str}[/bold]")
337
+ table.add_row("🕐 Stopped at", end_time.strftime('%Y-%m-%d %H:%M:%S'))
338
+
339
+ # Add extra info
340
+ for key, value in extra_info.items():
341
+ key_display = key.replace('_', ' ').title()
342
+ table.add_row(key_display, str(value))
343
+
344
+ # Create panel
345
+ panel = Panel(
346
+ table,
347
+ title=f"[bold red]🧹 Shutting down {server_type}[/bold red]",
348
+ border_style="red",
349
+ padding=(1, 2)
350
+ )
351
+
352
+ console.print(panel)
353
+ console.print("[green]✅ Server shutdown complete[/green]", style="bold")
354
+ else:
355
+ # Fallback to simple logging
356
+ logger.info("=" * 80)
357
+ logger.info(f"🧹 Shutting down {server_type}...")
358
+
359
+ if reason:
360
+ logger.info(f" 📋 Reason: {reason}")
361
+
362
+ logger.info(f" ⏱️ Uptime: {uptime_str}")
363
+ logger.info(f" 🕐 Stopped at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
364
+
365
+ for key, value in extra_info.items():
366
+ key_display = key.replace('_', ' ').title()
367
+ logger.info(f" {key_display}: {value}")
368
+
369
+ logger.info("✅ Server shutdown complete")
370
+ logger.info("=" * 80)
371
+
372
+
373
+ __all__ = [
374
+ "setup_streaming_logger",
375
+ "get_streaming_logger",
376
+ "log_server_start",
377
+ "log_server_shutdown",
378
+ ]
@@ -5,6 +5,8 @@ Serializers for displaying user's DjangoConfig settings.
5
5
  """
6
6
 
7
7
  from rest_framework import serializers
8
+ from drf_spectacular.utils import extend_schema_field
9
+ from drf_spectacular.types import OpenApiTypes
8
10
 
9
11
 
10
12
  # Nested serializers for complex structures
@@ -89,6 +91,18 @@ class RedisQueueConfigSerializer(serializers.Serializer):
89
91
  socket_timeout = serializers.IntegerField(required=False, allow_null=True)
90
92
 
91
93
 
94
+ class RQScheduleSerializer(serializers.Serializer):
95
+ """Redis Queue schedule configuration."""
96
+ func = serializers.CharField(required=False, allow_null=True)
97
+ cron_string = serializers.CharField(required=False, allow_null=True)
98
+ queue = serializers.CharField(required=False, allow_null=True)
99
+ kwargs = serializers.DictField(required=False, allow_null=True)
100
+ args = serializers.ListField(required=False, allow_null=True)
101
+ meta = serializers.DictField(required=False, allow_null=True)
102
+ repeat = serializers.IntegerField(required=False, allow_null=True)
103
+ result_ttl = serializers.IntegerField(required=False, allow_null=True)
104
+
105
+
92
106
  class DjangoRQConfigSerializer(serializers.Serializer):
93
107
  """Django-RQ configuration."""
94
108
  enabled = serializers.BooleanField(required=False, allow_null=True)
@@ -97,7 +111,7 @@ class DjangoRQConfigSerializer(serializers.Serializer):
97
111
  exception_handlers = serializers.ListField(required=False, allow_null=True)
98
112
  api_token = serializers.CharField(required=False, allow_null=True)
99
113
  prometheus_enabled = serializers.BooleanField(required=False, allow_null=True)
100
- schedules = serializers.ListField(child=serializers.DictField(), required=False, allow_null=True)
114
+ schedules = serializers.ListField(child=RQScheduleSerializer(), required=False, allow_null=True)
101
115
 
102
116
 
103
117
  class DRFConfigSerializer(serializers.Serializer):
@@ -126,6 +140,78 @@ class ConfigMetaSerializer(serializers.Serializer):
126
140
  secret_key_configured = serializers.BooleanField()
127
141
 
128
142
 
143
+ class TelegramConfigSerializer(serializers.Serializer):
144
+ """Telegram service configuration."""
145
+ bot_token = serializers.CharField(required=False, allow_null=True)
146
+ chat_id = serializers.IntegerField(required=False, allow_null=True)
147
+ parse_mode = serializers.CharField(required=False, allow_null=True)
148
+ disable_notification = serializers.BooleanField(required=False, allow_null=True)
149
+ disable_web_page_preview = serializers.BooleanField(required=False, allow_null=True)
150
+ timeout = serializers.IntegerField(required=False, allow_null=True)
151
+ webhook_url = serializers.CharField(required=False, allow_null=True)
152
+ webhook_secret = serializers.CharField(required=False, allow_null=True)
153
+ max_retries = serializers.IntegerField(required=False, allow_null=True)
154
+ retry_delay = serializers.FloatField(required=False, allow_null=True)
155
+
156
+
157
+ class NgrokConfigSerializer(serializers.Serializer):
158
+ """Ngrok tunneling configuration."""
159
+ enabled = serializers.BooleanField(required=False, allow_null=True)
160
+ authtoken = serializers.CharField(required=False, allow_null=True)
161
+ basic_auth = serializers.ListField(required=False, allow_null=True)
162
+ compression = serializers.BooleanField(required=False, allow_null=True)
163
+
164
+
165
+ class AxesConfigSerializer(serializers.Serializer):
166
+ """Django-Axes brute-force protection configuration."""
167
+ enabled = serializers.BooleanField(required=False, allow_null=True)
168
+ failure_limit = serializers.IntegerField(required=False, allow_null=True)
169
+ cooloff_time = serializers.IntegerField(required=False, allow_null=True)
170
+ lock_out_at_failure = serializers.BooleanField(required=False, allow_null=True)
171
+ reset_on_success = serializers.BooleanField(required=False, allow_null=True)
172
+ only_user_failures = serializers.BooleanField(required=False, allow_null=True)
173
+ lockout_template = serializers.CharField(required=False, allow_null=True)
174
+ lockout_url = serializers.CharField(required=False, allow_null=True)
175
+ verbose = serializers.BooleanField(required=False, allow_null=True)
176
+ enable_access_failure_log = serializers.BooleanField(required=False, allow_null=True)
177
+ ipware_proxy_count = serializers.IntegerField(required=False, allow_null=True)
178
+ ipware_meta_precedence_order = serializers.ListField(required=False, allow_null=True)
179
+ allowed_ips = serializers.ListField(required=False, allow_null=True)
180
+ denied_ips = serializers.ListField(required=False, allow_null=True)
181
+ cache_name = serializers.CharField(required=False, allow_null=True)
182
+ use_user_agent = serializers.BooleanField(required=False, allow_null=True)
183
+ username_form_field = serializers.CharField(required=False, allow_null=True)
184
+
185
+
186
+ class NextJSAdminConfigSerializer(serializers.Serializer):
187
+ """Next.js Admin application configuration."""
188
+ enabled = serializers.BooleanField(required=False, allow_null=True)
189
+ url = serializers.CharField(required=False, allow_null=True)
190
+ api_base_url = serializers.CharField(required=False, allow_null=True)
191
+
192
+
193
+ class ConstanceConfigSerializer(serializers.Serializer):
194
+ """Django-Constance dynamic settings configuration."""
195
+ config = serializers.DictField(required=False, allow_null=True)
196
+ config_fieldsets = serializers.DictField(required=False, allow_null=True)
197
+ backend = serializers.CharField(required=False, allow_null=True)
198
+ database_prefix = serializers.CharField(required=False, allow_null=True)
199
+ database_cache_backend = serializers.CharField(required=False, allow_null=True)
200
+ additional_config = serializers.DictField(required=False, allow_null=True)
201
+
202
+
203
+ class OpenAPIClientConfigSerializer(serializers.Serializer):
204
+ """OpenAPI Client generation configuration."""
205
+ enabled = serializers.BooleanField(required=False, allow_null=True)
206
+ output_dir = serializers.CharField(required=False, allow_null=True)
207
+ client_name = serializers.CharField(required=False, allow_null=True)
208
+ schema_url = serializers.CharField(required=False, allow_null=True)
209
+ generator = serializers.CharField(required=False, allow_null=True)
210
+ additional_properties = serializers.DictField(required=False, allow_null=True)
211
+ templates = serializers.ListField(required=False, allow_null=True)
212
+ global_properties = serializers.DictField(required=False, allow_null=True)
213
+
214
+
129
215
  class DjangoConfigSerializer(serializers.Serializer):
130
216
  """
131
217
  Typed serializer for user's DjangoConfig settings.
@@ -188,10 +274,10 @@ class DjangoConfigSerializer(serializers.Serializer):
188
274
  spectacular = SpectacularConfigSerializer(required=False, allow_null=True)
189
275
  jwt = JWTConfigSerializer(required=False, allow_null=True)
190
276
 
191
- # Other configs (pass-through for flexibility)
192
- telegram = serializers.DictField(required=False, allow_null=True)
193
- ngrok = serializers.DictField(required=False, allow_null=True)
194
- axes = serializers.DictField(required=False, allow_null=True)
277
+ # Services & Security (now typed!)
278
+ telegram = TelegramConfigSerializer(required=False, allow_null=True)
279
+ ngrok = NgrokConfigSerializer(required=False, allow_null=True)
280
+ axes = AxesConfigSerializer(required=False, allow_null=True)
195
281
  crypto_fields = serializers.DictField(required=False, allow_null=True)
196
282
  unfold = serializers.CharField(required=False, allow_null=True) # String representation of Unfold config
197
283
  tailwind_app_name = serializers.CharField(required=False, allow_null=True)
@@ -199,10 +285,10 @@ class DjangoConfigSerializer(serializers.Serializer):
199
285
  limits = serializers.DictField(required=False, allow_null=True)
200
286
  api_keys = serializers.DictField(required=False, allow_null=True)
201
287
  custom_middleware = serializers.ListField(required=False, allow_null=True)
202
- nextjs_admin = serializers.DictField(required=False, allow_null=True)
288
+ nextjs_admin = NextJSAdminConfigSerializer(required=False, allow_null=True)
203
289
  admin_emails = serializers.ListField(required=False, allow_null=True)
204
- constance = serializers.DictField(required=False, allow_null=True)
205
- openapi_client = serializers.DictField(required=False, allow_null=True)
290
+ constance = ConstanceConfigSerializer(required=False, allow_null=True)
291
+ openapi_client = OpenAPIClientConfigSerializer(required=False, allow_null=True)
206
292
 
207
293
  # Metadata
208
294
  _meta = ConfigMetaSerializer(required=False, allow_null=True)
@@ -238,7 +324,7 @@ class ConfigDataSerializer(serializers.Serializer):
238
324
  help_text="User's DjangoConfig settings"
239
325
  )
240
326
  django_settings = serializers.DictField(
241
- help_text="Complete Django settings (sanitized)"
327
+ help_text="Complete Django settings (sanitized, contains mixed types)"
242
328
  )
243
329
  _validation = ConfigValidationSerializer(
244
330
  help_text="Validation result comparing serializer with actual config"
@@ -36,11 +36,16 @@ class UserStatisticsSerializer(serializers.Serializer):
36
36
  superusers = serializers.IntegerField(help_text="Number of superusers")
37
37
 
38
38
 
39
+ class AppStatisticsDataSerializer(serializers.Serializer):
40
+ """Serializer for application statistics data."""
41
+
42
+ name = serializers.CharField(help_text="Human-readable app name")
43
+ total_records = serializers.IntegerField(help_text="Total records count")
44
+ model_count = serializers.IntegerField(help_text="Number of models")
45
+
46
+
39
47
  class AppStatisticsSerializer(serializers.Serializer):
40
48
  """Serializer for application-specific statistics."""
41
49
 
42
50
  app_name = serializers.CharField(help_text="Application name")
43
- statistics = serializers.DictField(
44
- child=serializers.IntegerField(),
45
- help_text="Application statistics"
46
- )
51
+ statistics = AppStatisticsDataSerializer(help_text="Application statistics")