django-cfg 1.4.62__py3-none-any.whl → 1.4.63__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 (181) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/services/otp_service.py +3 -14
  3. django_cfg/apps/centrifugo/__init__.py +57 -0
  4. django_cfg/apps/centrifugo/admin/__init__.py +13 -0
  5. django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
  6. django_cfg/apps/centrifugo/admin/config.py +82 -0
  7. django_cfg/apps/centrifugo/apps.py +31 -0
  8. django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
  9. django_cfg/apps/centrifugo/codegen/README.md +242 -0
  10. django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
  11. django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
  12. django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
  13. django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
  14. django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
  15. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
  16. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
  17. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
  18. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
  19. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
  20. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
  21. django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
  22. django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
  23. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
  24. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
  25. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
  26. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
  27. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
  28. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
  29. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
  30. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
  31. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
  32. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
  33. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
  34. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
  35. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
  36. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
  37. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
  38. django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
  39. django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
  40. django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
  41. django_cfg/apps/centrifugo/decorators.py +137 -0
  42. django_cfg/apps/centrifugo/management/__init__.py +1 -0
  43. django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
  44. django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
  45. django_cfg/apps/centrifugo/managers/__init__.py +12 -0
  46. django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
  47. django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
  48. django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
  49. django_cfg/apps/centrifugo/models/__init__.py +11 -0
  50. django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
  51. django_cfg/apps/centrifugo/registry.py +106 -0
  52. django_cfg/apps/centrifugo/router.py +125 -0
  53. django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
  54. django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
  55. django_cfg/apps/centrifugo/serializers/channels.py +26 -0
  56. django_cfg/apps/centrifugo/serializers/health.py +17 -0
  57. django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
  58. django_cfg/apps/centrifugo/serializers/stats.py +21 -0
  59. django_cfg/apps/centrifugo/services/__init__.py +12 -0
  60. django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
  61. django_cfg/apps/centrifugo/services/client/client.py +577 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +228 -0
  63. django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
  64. django_cfg/apps/centrifugo/services/config_helper.py +63 -0
  65. django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
  66. django_cfg/apps/centrifugo/services/logging.py +677 -0
  67. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
  68. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
  69. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
  70. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
  71. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
  72. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
  73. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
  77. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
  78. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
  79. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
  80. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
  81. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
  82. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
  83. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
  84. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
  85. django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
  86. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
  87. django_cfg/apps/centrifugo/urls.py +31 -0
  88. django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
  89. django_cfg/apps/centrifugo/views/__init__.py +15 -0
  90. django_cfg/apps/centrifugo/views/admin_api.py +374 -0
  91. django_cfg/apps/centrifugo/views/dashboard.py +15 -0
  92. django_cfg/apps/centrifugo/views/monitoring.py +286 -0
  93. django_cfg/apps/centrifugo/views/testing_api.py +422 -0
  94. django_cfg/apps/support/utils/support_email_service.py +5 -18
  95. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
  96. django_cfg/apps/urls.py +5 -5
  97. django_cfg/core/base/config_model.py +4 -44
  98. django_cfg/core/builders/apps_builder.py +2 -2
  99. django_cfg/core/generation/integration_generators/third_party.py +8 -8
  100. django_cfg/core/utils/__init__.py +5 -0
  101. django_cfg/core/utils/url_helpers.py +73 -0
  102. django_cfg/modules/base.py +7 -7
  103. django_cfg/modules/django_client/core/__init__.py +2 -1
  104. django_cfg/modules/django_client/core/config/config.py +8 -0
  105. django_cfg/modules/django_client/core/generator/__init__.py +42 -2
  106. django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
  107. django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
  108. django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
  109. django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
  110. django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
  111. django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
  112. django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
  113. django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
  114. django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
  115. django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
  116. django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
  117. django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
  118. django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
  119. django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
  120. django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
  121. django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
  122. django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
  123. django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
  124. django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
  125. django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
  126. django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
  127. django_cfg/modules/django_client/system/schema_parser.py +5 -1
  128. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
  129. django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
  130. django_cfg/modules/django_unfold/dashboard.py +25 -19
  131. django_cfg/pyproject.toml +1 -1
  132. django_cfg/registry/core.py +2 -0
  133. django_cfg/registry/modules.py +2 -2
  134. django_cfg/static/js/api/centrifugo/client.mjs +164 -0
  135. django_cfg/static/js/api/centrifugo/index.mjs +13 -0
  136. django_cfg/static/js/api/index.mjs +5 -5
  137. django_cfg/static/js/api/types.mjs +89 -26
  138. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -70
  140. django_cfg/apps/ipc/README.md +0 -346
  141. django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
  142. django_cfg/apps/ipc/TESTING.md +0 -539
  143. django_cfg/apps/ipc/__init__.py +0 -60
  144. django_cfg/apps/ipc/admin.py +0 -232
  145. django_cfg/apps/ipc/apps.py +0 -98
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
  148. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  149. django_cfg/apps/ipc/models.py +0 -229
  150. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  151. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  152. django_cfg/apps/ipc/services/__init__.py +0 -7
  153. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  154. django_cfg/apps/ipc/services/client/client.py +0 -621
  155. django_cfg/apps/ipc/services/client/config.py +0 -214
  156. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  157. django_cfg/apps/ipc/services/logging.py +0 -239
  158. django_cfg/apps/ipc/services/monitor.py +0 -466
  159. django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  162. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  163. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  170. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  171. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  172. django_cfg/apps/ipc/urls.py +0 -23
  173. django_cfg/apps/ipc/views/__init__.py +0 -13
  174. django_cfg/apps/ipc/views/dashboard.py +0 -15
  175. django_cfg/apps/ipc/views/monitoring.py +0 -251
  176. django_cfg/apps/ipc/views/testing.py +0 -285
  177. django_cfg/static/js/api/ipc/client.mjs +0 -114
  178. django_cfg/static/js/api/ipc/index.mjs +0 -13
  179. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.4.62"
35
+ __version__ = "1.4.63"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
@@ -5,6 +5,7 @@ from typing import Optional
5
5
  from django.db import transaction
6
6
  from django.utils import timezone
7
7
 
8
+ from django_cfg.core.utils import get_otp_url
8
9
  from django_cfg.modules.django_telegram import DjangoTelegram
9
10
 
10
11
  from ..models import CustomUser, OTPSecret
@@ -17,20 +18,8 @@ logger = logging.getLogger(__name__)
17
18
  class OTPService:
18
19
  """Simple OTP service for authentication."""
19
20
 
20
- @staticmethod
21
- def _get_otp_url(otp_code: str) -> str:
22
- """Get OTP verification URL from configuration."""
23
- try:
24
- from django_cfg.core.state import get_current_config
25
- config = get_current_config()
26
- if config and hasattr(config, 'get_otp_url'):
27
- return config.get_otp_url(otp_code)
28
- else:
29
- # Fallback URL if config is not available
30
- return f"#otp-{otp_code}"
31
- except Exception as e:
32
- logger.warning(f"Could not generate OTP URL: {e}")
33
- return f"#otp-{otp_code}"
21
+ # Expose get_otp_url as a static method for backward compatibility
22
+ _get_otp_url = staticmethod(get_otp_url)
34
23
 
35
24
  @staticmethod
36
25
  @transaction.atomic
@@ -0,0 +1,57 @@
1
+ """
2
+ Django-CFG Centrifugo Integration.
3
+
4
+ Provides Django integration with Centrifugo WebSocket server for real-time
5
+ notifications with delivery confirmation (ACK tracking).
6
+
7
+ Main Components:
8
+ - CentrifugoClient: Django client for publishing messages
9
+ - CentrifugoLog: Model for tracking publish operations
10
+ - CentrifugoLogger: Helper for automatic logging
11
+ - DjangoCfgCentrifugoConfig: Pydantic configuration model
12
+
13
+ Example:
14
+ >>> from django_cfg.apps.centrifugo import get_centrifugo_client
15
+ >>>
16
+ >>> client = get_centrifugo_client()
17
+ >>> result = await client.publish_with_ack(
18
+ ... channel="user#123",
19
+ ... data={"title": "Hello", "message": "World"},
20
+ ... ack_timeout=10
21
+ ... )
22
+ >>> print(f"Delivered: {result.delivered}")
23
+ """
24
+
25
+ from .services.client.client import CentrifugoClient, get_centrifugo_client, PublishResponse
26
+ from .services.client.config import DjangoCfgCentrifugoConfig
27
+ from .services.client.exceptions import (
28
+ CentrifugoBaseException,
29
+ CentrifugoConfigurationError,
30
+ CentrifugoConnectionError,
31
+ CentrifugoPublishError,
32
+ CentrifugoTimeoutError,
33
+ CentrifugoValidationError,
34
+ )
35
+ from .services.logging import CentrifugoLogContext, CentrifugoLogger
36
+
37
+ # Note: CentrifugoLog model is not imported here to avoid AppRegistryNotReady errors
38
+ # Import it directly from .models when needed: from django_cfg.apps.centrifugo.models import CentrifugoLog
39
+
40
+ __all__ = [
41
+ # Config
42
+ "DjangoCfgCentrifugoConfig",
43
+ # Client
44
+ "CentrifugoClient",
45
+ "get_centrifugo_client",
46
+ "PublishResponse",
47
+ # Logging
48
+ "CentrifugoLogger",
49
+ "CentrifugoLogContext",
50
+ # Exceptions
51
+ "CentrifugoBaseException",
52
+ "CentrifugoTimeoutError",
53
+ "CentrifugoPublishError",
54
+ "CentrifugoConnectionError",
55
+ "CentrifugoConfigurationError",
56
+ "CentrifugoValidationError",
57
+ ]
@@ -0,0 +1,13 @@
1
+ """
2
+ Django Admin for Centrifugo models.
3
+
4
+ Uses PydanticAdmin with declarative configuration.
5
+ """
6
+
7
+ from .centrifugo_log import CentrifugoLogAdmin
8
+ from .config import centrifugolog_config
9
+
10
+ __all__ = [
11
+ "centrifugolog_config",
12
+ "CentrifugoLogAdmin",
13
+ ]
@@ -0,0 +1,249 @@
1
+ """
2
+ Centrifugo Log Admin.
3
+
4
+ PydanticAdmin for CentrifugoLog model with custom computed fields.
5
+ """
6
+
7
+ import json
8
+
9
+ from django.contrib import admin
10
+ from django_cfg.modules.django_admin import Icons, computed_field
11
+ from django_cfg.modules.django_admin.base import PydanticAdmin
12
+
13
+ from ..models import CentrifugoLog
14
+ from .config import centrifugolog_config
15
+
16
+
17
+ @admin.register(CentrifugoLog)
18
+ class CentrifugoLogAdmin(PydanticAdmin):
19
+ """
20
+ Centrifugo log admin with analytics and filtering.
21
+
22
+ Features:
23
+ - Color-coded status badges
24
+ - ACK tracking visualization
25
+ - Duration display with performance indicators
26
+ - Formatted JSON for message data
27
+ - Error details with highlighted display
28
+ """
29
+
30
+ config = centrifugolog_config
31
+
32
+ @computed_field("Type", ordering="wait_for_ack")
33
+ def type_badge(self, obj):
34
+ """Display badge showing if this publish waited for ACK."""
35
+ if obj.wait_for_ack:
36
+ return self.html.badge("ACK", variant="success", icon=Icons.CHECK_CIRCLE)
37
+ else:
38
+ return self.html.badge("FIRE", variant="secondary", icon=Icons.NOTIFICATIONS)
39
+
40
+ @computed_field("ACKs", ordering="acks_received")
41
+ def acks_display(self, obj):
42
+ """Display ACK count with context."""
43
+ if not obj.wait_for_ack:
44
+ return self.html.empty()
45
+
46
+ # If we have expected count
47
+ if obj.acks_expected:
48
+ rate = obj.delivery_rate
49
+ if rate == 1.0:
50
+ variant = "success"
51
+ icon = Icons.CHECK_CIRCLE
52
+ elif rate > 0:
53
+ variant = "warning"
54
+ icon = Icons.TIMER
55
+ else:
56
+ variant = "danger"
57
+ icon = Icons.ERROR
58
+
59
+ return self.html.badge(
60
+ f"{obj.acks_received}/{obj.acks_expected}",
61
+ variant=variant,
62
+ icon=icon,
63
+ )
64
+
65
+ # Just show count
66
+ if obj.acks_received > 0:
67
+ return self.html.badge(
68
+ f"{obj.acks_received} ACKs", variant="success", icon=Icons.CHECK_CIRCLE
69
+ )
70
+ else:
71
+ return self.html.badge("0 ACKs", variant="danger", icon=Icons.ERROR)
72
+
73
+ @computed_field("Duration", ordering="duration_ms")
74
+ def duration_display(self, obj):
75
+ """Display duration with color coding based on speed."""
76
+ if obj.duration_ms is None:
77
+ return self.html.empty()
78
+
79
+ # Color code based on duration
80
+ if obj.duration_ms < 100:
81
+ variant = "success" # Fast
82
+ icon = Icons.SPEED
83
+ elif obj.duration_ms < 500:
84
+ variant = "warning" # Moderate
85
+ icon = Icons.TIMER
86
+ else:
87
+ variant = "danger" # Slow
88
+ icon = Icons.ERROR
89
+
90
+ return self.html.badge(f"{obj.duration_ms}ms", variant=variant, icon=icon)
91
+
92
+ def data_display(self, obj):
93
+ """Display formatted JSON message data."""
94
+ if not obj.data:
95
+ return self.html.empty("No data")
96
+
97
+ try:
98
+ formatted = json.dumps(obj.data, indent=2)
99
+ return f'<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto; font-size: 12px; line-height: 1.5;">{formatted}</pre>'
100
+ except Exception:
101
+ return str(obj.data)
102
+
103
+ data_display.short_description = "Message Data"
104
+
105
+ def error_details_display(self, obj):
106
+ """Display error information if publish failed."""
107
+ if obj.is_successful or obj.status == "pending":
108
+ return self.html.inline(
109
+ [
110
+ self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
111
+ self.html.span("No errors", "text-green-600"),
112
+ ]
113
+ )
114
+
115
+ details = []
116
+
117
+ if obj.error_code:
118
+ details.append(
119
+ self.html.inline(
120
+ [
121
+ self.html.span("Error Code:", "font-semibold"),
122
+ self.html.badge(obj.error_code, variant="danger", icon=Icons.ERROR),
123
+ ],
124
+ separator=" ",
125
+ )
126
+ )
127
+
128
+ if obj.error_message:
129
+ details.append(
130
+ self.html.inline(
131
+ [
132
+ self.html.span("Message:", "font-semibold"),
133
+ self.html.span(obj.error_message, "text-red-600"),
134
+ ],
135
+ separator=" ",
136
+ )
137
+ )
138
+
139
+ return "<br>".join(details) if details else self.html.empty()
140
+
141
+ error_details_display.short_description = "Error Details"
142
+
143
+ def delivery_stats_display(self, obj):
144
+ """Display delivery statistics."""
145
+ if not obj.wait_for_ack:
146
+ return self.html.empty("No ACK tracking")
147
+
148
+ stats = []
149
+
150
+ # ACK timeout
151
+ if obj.ack_timeout:
152
+ stats.append(
153
+ self.html.inline(
154
+ [
155
+ self.html.span("Timeout:", "font-semibold"),
156
+ self.html.span(f"{obj.ack_timeout}s", "text-gray-600"),
157
+ ],
158
+ separator=" ",
159
+ )
160
+ )
161
+
162
+ # ACKs received
163
+ stats.append(
164
+ self.html.inline(
165
+ [
166
+ self.html.span("ACKs Received:", "font-semibold"),
167
+ self.html.badge(str(obj.acks_received), variant="info"),
168
+ ],
169
+ separator=" ",
170
+ )
171
+ )
172
+
173
+ # ACKs expected (if known)
174
+ if obj.acks_expected:
175
+ stats.append(
176
+ self.html.inline(
177
+ [
178
+ self.html.span("ACKs Expected:", "font-semibold"),
179
+ self.html.span(str(obj.acks_expected), "text-gray-600"),
180
+ ],
181
+ separator=" ",
182
+ )
183
+ )
184
+
185
+ # Delivery rate
186
+ if obj.delivery_rate is not None:
187
+ rate_pct = obj.delivery_rate * 100
188
+ stats.append(
189
+ self.html.inline(
190
+ [
191
+ self.html.span("Delivery Rate:", "font-semibold"),
192
+ self.html.span(f"{rate_pct:.1f}%", "text-blue-600"),
193
+ ],
194
+ separator=" ",
195
+ )
196
+ )
197
+
198
+ return "<br>".join(stats) if stats else self.html.empty()
199
+
200
+ delivery_stats_display.short_description = "Delivery Statistics"
201
+
202
+ # Fieldsets for detail view
203
+ def get_fieldsets(self, request, obj=None):
204
+ """Dynamic fieldsets based on object state."""
205
+ fieldsets = [
206
+ (
207
+ "Publish Information",
208
+ {"fields": ("id", "message_id", "channel", "user", "status")},
209
+ ),
210
+ (
211
+ "Message Data",
212
+ {"fields": ("data_display",), "classes": ("collapse",)},
213
+ ),
214
+ ("Performance", {"fields": ("duration_ms", "created_at", "completed_at")}),
215
+ ("Metadata", {"fields": ("caller_ip", "user_agent"), "classes": ("collapse",)}),
216
+ ]
217
+
218
+ # Add ACK tracking section if enabled
219
+ if obj and obj.wait_for_ack:
220
+ fieldsets.insert(
221
+ 2,
222
+ (
223
+ "ACK Tracking",
224
+ {
225
+ "fields": (
226
+ "delivery_stats_display",
227
+ "wait_for_ack",
228
+ "ack_timeout",
229
+ "acks_received",
230
+ "acks_expected",
231
+ )
232
+ },
233
+ ),
234
+ )
235
+
236
+ # Add error section only if failed
237
+ if obj and not obj.is_successful and obj.status != "pending":
238
+ fieldsets.insert(
239
+ 3,
240
+ (
241
+ "Error Details",
242
+ {"fields": ("error_details_display", "error_code", "error_message")},
243
+ ),
244
+ )
245
+
246
+ return fieldsets
247
+
248
+
249
+ __all__ = ["CentrifugoLogAdmin"]
@@ -0,0 +1,82 @@
1
+ """
2
+ Admin configuration for Centrifugo models.
3
+
4
+ Declarative AdminConfig using PydanticAdmin patterns.
5
+ """
6
+
7
+ from django_cfg.modules.django_admin import (
8
+ AdminConfig,
9
+ BadgeField,
10
+ DateTimeField,
11
+ Icons,
12
+ UserField,
13
+ )
14
+
15
+ from ..models import CentrifugoLog
16
+
17
+
18
+ # Declarative configuration for CentrifugoLog
19
+ centrifugolog_config = AdminConfig(
20
+ model=CentrifugoLog,
21
+ # Performance optimization
22
+ select_related=["user"],
23
+
24
+ # List display
25
+ list_display=[
26
+ "channel",
27
+ "type_badge",
28
+ "status",
29
+ "user",
30
+ "acks_display",
31
+ "duration_display",
32
+ "created_at",
33
+ "completed_at"
34
+ ],
35
+
36
+ # Auto-generated display methods
37
+ display_fields=[
38
+ BadgeField(name="channel", title="Channel", variant="info", icon=Icons.NOTIFICATIONS),
39
+ BadgeField(
40
+ name="status",
41
+ title="Status",
42
+ label_map={
43
+ "pending": "warning",
44
+ "success": "success",
45
+ "failed": "danger",
46
+ "timeout": "danger",
47
+ "partial": "warning",
48
+ },
49
+ ),
50
+ UserField(name="user", title="User", header=True),
51
+ DateTimeField(name="created_at", title="Created", ordering="created_at"),
52
+ DateTimeField(name="completed_at", title="Completed", ordering="completed_at"),
53
+ ],
54
+ # Filters
55
+ list_filter=["status", "wait_for_ack", "channel", "created_at"],
56
+ search_fields=[
57
+ "message_id",
58
+ "channel",
59
+ "user__username",
60
+ "user__email",
61
+ "error_message",
62
+ ],
63
+ # Autocomplete for user field
64
+ autocomplete_fields=["user"],
65
+ # Readonly fields
66
+ readonly_fields=[
67
+ "id",
68
+ "message_id",
69
+ "created_at",
70
+ "completed_at",
71
+ "data_display",
72
+ "error_details_display",
73
+ "delivery_stats_display",
74
+ ],
75
+ # Date hierarchy
76
+ date_hierarchy="created_at",
77
+ # Per page
78
+ list_per_page=50,
79
+ )
80
+
81
+
82
+ __all__ = ["centrifugolog_config"]
@@ -0,0 +1,31 @@
1
+ """
2
+ Django app configuration for Centrifugo module.
3
+
4
+ Provides Centrifugo pub/sub client with ACK tracking.
5
+ """
6
+
7
+ from django.apps import AppConfig
8
+
9
+
10
+ class CentrifugoConfig(AppConfig):
11
+ """
12
+ Centrifugo application configuration.
13
+
14
+ Provides:
15
+ - Async client for publishing messages to Centrifugo
16
+ - ACK tracking for delivery confirmation
17
+ - Logging of all publish operations
18
+ - Migration-friendly API (mirrors django-ipc patterns)
19
+ """
20
+
21
+ default_auto_field = "django.db.models.BigAutoField"
22
+ name = "django_cfg.apps.centrifugo"
23
+ label = "django_cfg_centrifugo"
24
+ verbose_name = "Centrifugo WebSocket"
25
+
26
+ def ready(self):
27
+ """Initialize app when Django starts."""
28
+ from django_cfg.modules.django_logging import get_logger
29
+
30
+ logger = get_logger("centrifugo.apps")
31
+ logger.info("Centrifugo app initialized")