django-cfg 1.4.120__py3-none-any.whl → 1.5.2__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 (182) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +0 -12
  5. django_cfg/apps/dashboard/serializers/activity.py +1 -1
  6. django_cfg/apps/dashboard/services/__init__.py +0 -2
  7. django_cfg/apps/dashboard/services/charts_service.py +4 -3
  8. django_cfg/apps/dashboard/services/statistics_service.py +11 -2
  9. django_cfg/apps/dashboard/services/system_health_service.py +64 -106
  10. django_cfg/apps/dashboard/urls.py +0 -2
  11. django_cfg/apps/dashboard/views/__init__.py +0 -2
  12. django_cfg/apps/dashboard/views/commands_views.py +3 -6
  13. django_cfg/apps/dashboard/views/overview_views.py +14 -13
  14. django_cfg/apps/grpc/__init__.py +9 -0
  15. django_cfg/apps/grpc/admin/__init__.py +11 -0
  16. django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
  17. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  18. django_cfg/apps/grpc/apps.py +28 -0
  19. django_cfg/apps/grpc/auth/__init__.py +9 -0
  20. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  21. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  22. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  23. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  24. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  25. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  26. django_cfg/apps/grpc/management/__init__.py +1 -0
  27. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  28. django_cfg/apps/grpc/managers/__init__.py +10 -0
  29. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  30. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  31. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  32. django_cfg/apps/grpc/models/__init__.py +9 -0
  33. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  34. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  35. django_cfg/apps/grpc/serializers/health.py +18 -0
  36. django_cfg/apps/grpc/serializers/requests.py +18 -0
  37. django_cfg/apps/grpc/serializers/services.py +50 -0
  38. django_cfg/apps/grpc/serializers/stats.py +22 -0
  39. django_cfg/apps/grpc/services/__init__.py +16 -0
  40. django_cfg/apps/grpc/services/base.py +375 -0
  41. django_cfg/apps/grpc/services/discovery.py +415 -0
  42. django_cfg/apps/grpc/urls.py +23 -0
  43. django_cfg/apps/grpc/utils/__init__.py +13 -0
  44. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  45. django_cfg/apps/grpc/views/__init__.py +9 -0
  46. django_cfg/apps/grpc/views/monitoring.py +497 -0
  47. django_cfg/apps/knowbase/apps.py +2 -2
  48. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
  49. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  50. django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
  51. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  52. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  53. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  54. django_cfg/apps/rq/__init__.py +9 -0
  55. django_cfg/apps/rq/apps.py +80 -0
  56. django_cfg/apps/rq/management/__init__.py +1 -0
  57. django_cfg/apps/rq/management/commands/__init__.py +1 -0
  58. django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
  59. django_cfg/apps/rq/management/commands/rqstats.py +33 -0
  60. django_cfg/apps/rq/management/commands/rqworker.py +31 -0
  61. django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
  62. django_cfg/apps/rq/serializers/__init__.py +40 -0
  63. django_cfg/apps/rq/serializers/health.py +60 -0
  64. django_cfg/apps/rq/serializers/job.py +100 -0
  65. django_cfg/apps/rq/serializers/queue.py +80 -0
  66. django_cfg/apps/rq/serializers/schedule.py +178 -0
  67. django_cfg/apps/rq/serializers/testing.py +139 -0
  68. django_cfg/apps/rq/serializers/worker.py +58 -0
  69. django_cfg/apps/rq/services/__init__.py +25 -0
  70. django_cfg/apps/rq/services/config_helper.py +233 -0
  71. django_cfg/apps/rq/services/models/README.md +417 -0
  72. django_cfg/apps/rq/services/models/__init__.py +30 -0
  73. django_cfg/apps/rq/services/models/event.py +123 -0
  74. django_cfg/apps/rq/services/models/job.py +99 -0
  75. django_cfg/apps/rq/services/models/queue.py +92 -0
  76. django_cfg/apps/rq/services/models/worker.py +104 -0
  77. django_cfg/apps/rq/services/rq_converters.py +183 -0
  78. django_cfg/apps/rq/tasks/__init__.py +23 -0
  79. django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
  80. django_cfg/apps/rq/urls.py +54 -0
  81. django_cfg/apps/rq/views/__init__.py +19 -0
  82. django_cfg/apps/rq/views/jobs.py +882 -0
  83. django_cfg/apps/rq/views/monitoring.py +248 -0
  84. django_cfg/apps/rq/views/queues.py +261 -0
  85. django_cfg/apps/rq/views/schedule.py +400 -0
  86. django_cfg/apps/rq/views/testing.py +761 -0
  87. django_cfg/apps/rq/views/workers.py +195 -0
  88. django_cfg/apps/urls.py +13 -8
  89. django_cfg/config.py +106 -0
  90. django_cfg/core/base/config_model.py +16 -26
  91. django_cfg/core/builders/apps_builder.py +7 -11
  92. django_cfg/core/generation/integration_generators/__init__.py +3 -6
  93. django_cfg/core/generation/integration_generators/django_rq.py +80 -0
  94. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  95. django_cfg/core/generation/orchestrator.py +15 -15
  96. django_cfg/core/integration/display/startup.py +6 -20
  97. django_cfg/mixins/__init__.py +2 -0
  98. django_cfg/mixins/superadmin_api.py +59 -0
  99. django_cfg/models/__init__.py +3 -3
  100. django_cfg/models/api/grpc/__init__.py +59 -0
  101. django_cfg/models/api/grpc/config.py +364 -0
  102. django_cfg/models/django/__init__.py +3 -3
  103. django_cfg/models/django/django_rq.py +621 -0
  104. django_cfg/models/django/revolution_legacy.py +1 -1
  105. django_cfg/modules/base.py +19 -6
  106. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  107. django_cfg/modules/django_admin/config/background_task_config.py +4 -4
  108. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  109. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  110. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  111. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  112. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  113. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  114. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  115. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  116. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  117. django_cfg/modules/django_admin/utils/html/composition.py +205 -0
  118. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  119. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  120. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  121. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  122. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  123. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  124. django_cfg/modules/django_unfold/navigation.py +21 -18
  125. django_cfg/pyproject.toml +4 -6
  126. django_cfg/registry/core.py +4 -7
  127. django_cfg/registry/modules.py +6 -0
  128. django_cfg/static/frontend/admin.zip +0 -0
  129. django_cfg/templates/admin/constance/includes/results_list.html +73 -0
  130. django_cfg/templates/admin/index.html +187 -62
  131. django_cfg/templatetags/django_cfg.py +61 -1
  132. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
  133. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
  134. django_cfg/apps/dashboard/permissions.py +0 -48
  135. django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
  136. django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
  137. django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
  138. django_cfg/apps/tasks/__init__.py +0 -64
  139. django_cfg/apps/tasks/admin/__init__.py +0 -4
  140. django_cfg/apps/tasks/admin/task_log.py +0 -265
  141. django_cfg/apps/tasks/apps.py +0 -15
  142. django_cfg/apps/tasks/filters/__init__.py +0 -10
  143. django_cfg/apps/tasks/filters/task_log.py +0 -121
  144. django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
  145. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
  146. django_cfg/apps/tasks/models/__init__.py +0 -4
  147. django_cfg/apps/tasks/models/task_log.py +0 -246
  148. django_cfg/apps/tasks/serializers/__init__.py +0 -28
  149. django_cfg/apps/tasks/serializers/task_log.py +0 -249
  150. django_cfg/apps/tasks/services/__init__.py +0 -10
  151. django_cfg/apps/tasks/services/client/__init__.py +0 -7
  152. django_cfg/apps/tasks/services/client/client.py +0 -234
  153. django_cfg/apps/tasks/services/config_helper.py +0 -63
  154. django_cfg/apps/tasks/services/sync.py +0 -204
  155. django_cfg/apps/tasks/urls.py +0 -16
  156. django_cfg/apps/tasks/views/__init__.py +0 -10
  157. django_cfg/apps/tasks/views/task_log.py +0 -41
  158. django_cfg/apps/tasks/views/task_log_base.py +0 -41
  159. django_cfg/apps/tasks/views/task_log_overview.py +0 -100
  160. django_cfg/apps/tasks/views/task_log_related.py +0 -41
  161. django_cfg/apps/tasks/views/task_log_stats.py +0 -91
  162. django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
  163. django_cfg/core/generation/integration_generators/django_q2.py +0 -133
  164. django_cfg/core/generation/integration_generators/tasks.py +0 -88
  165. django_cfg/models/django/django_q2.py +0 -514
  166. django_cfg/models/tasks/__init__.py +0 -49
  167. django_cfg/models/tasks/backends.py +0 -122
  168. django_cfg/models/tasks/config.py +0 -209
  169. django_cfg/models/tasks/utils.py +0 -162
  170. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  171. django_cfg/modules/django_q2/README.md +0 -140
  172. django_cfg/modules/django_q2/__init__.py +0 -8
  173. django_cfg/modules/django_q2/apps.py +0 -107
  174. django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
  176. /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
  177. /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
  178. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  179. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  180. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
  181. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
  182. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -8,6 +8,7 @@ Endpoint for complete dashboard overview:
8
8
  import logging
9
9
  from datetime import datetime
10
10
 
11
+ from django.db import transaction
11
12
  from drf_spectacular.utils import extend_schema
12
13
  from rest_framework import status, viewsets
13
14
 
@@ -32,6 +33,11 @@ class OverviewViewSet(AdminAPIMixin, viewsets.GenericViewSet):
32
33
 
33
34
  serializer_class = DashboardOverviewSerializer
34
35
 
36
+ @transaction.non_atomic_requests
37
+ def dispatch(self, request, *args, **kwargs):
38
+ """Disable atomic requests for this viewset."""
39
+ return super().dispatch(request, *args, **kwargs)
40
+
35
41
  @extend_schema(
36
42
  summary="Get dashboard overview",
37
43
  description="Retrieve complete dashboard data including stats, health, actions, and metrics",
@@ -40,24 +46,19 @@ class OverviewViewSet(AdminAPIMixin, viewsets.GenericViewSet):
40
46
  )
41
47
  @action(detail=False, methods=['get'], url_path='', url_name='overview')
42
48
  def overview(self, request):
43
- """
44
- Get complete dashboard overview.
45
-
46
- Returns all dashboard data in a single request:
47
- - Statistics cards
48
- - System health status
49
- - Quick actions
50
- - Recent activity
51
- - System metrics
52
- - User statistics
53
- """
49
+ """Get complete dashboard overview."""
54
50
  try:
55
51
  stats_service = StatisticsService()
56
52
  health_service = SystemHealthService()
57
53
  charts_service = ChartsService()
58
54
 
59
- # Get app statistics and convert to list format
60
- app_stats_dict = stats_service.get_app_statistics()
55
+ # Get app statistics - wrapped in try/except since it queries all models
56
+ try:
57
+ app_stats_dict = stats_service.get_app_statistics()
58
+ except Exception as e:
59
+ logger.error(f"Error getting app stats: {e}")
60
+ app_stats_dict = {'apps': {}}
61
+
61
62
  app_statistics_list = [
62
63
  {
63
64
  'app_name': app_label,
@@ -0,0 +1,9 @@
1
+ """
2
+ django_cfg.apps.grpc
3
+
4
+ gRPC integration for django-cfg with Django ORM, JWT auth, monitoring, and admin interface.
5
+ """
6
+
7
+ default_app_config = 'django_cfg.apps.grpc.apps.GRPCAppConfig'
8
+
9
+ __version__ = '1.0.0'
@@ -0,0 +1,11 @@
1
+ """
2
+ Admin interface for gRPC app.
3
+ """
4
+
5
+ from .config import grpcrequestlog_config
6
+ from .grpc_request_log import GRPCRequestLogAdmin
7
+
8
+ __all__ = [
9
+ "GRPCRequestLogAdmin",
10
+ "grpcrequestlog_config",
11
+ ]
@@ -1,5 +1,5 @@
1
1
  """
2
- Admin configuration for Task models.
2
+ Admin configuration for gRPC models.
3
3
 
4
4
  Declarative AdminConfig using PydanticAdmin patterns.
5
5
  """
@@ -12,87 +12,78 @@ from django_cfg.modules.django_admin import (
12
12
  UserField,
13
13
  )
14
14
 
15
- from ..models import TaskLog
15
+ from ..models import GRPCRequestLog
16
16
 
17
17
 
18
- # Declarative configuration for TaskLog
19
- tasklog_config = AdminConfig(
20
- model=TaskLog,
21
-
18
+ # Declarative configuration for GRPCRequestLog
19
+ grpcrequestlog_config = AdminConfig(
20
+ model=GRPCRequestLog,
22
21
  # Performance optimization
23
22
  select_related=["user"],
24
23
 
25
24
  # List display
26
25
  list_display=[
27
- "task_name",
28
- "queue_badge",
29
- "status_badge",
26
+ "full_method",
27
+ "service_badge",
28
+ "method_badge",
29
+ "status",
30
+ "grpc_status_code_display",
30
31
  "user",
31
32
  "duration_display",
32
- "retry_count",
33
33
  "created_at",
34
- "completed_at",
34
+ "completed_at"
35
35
  ],
36
36
 
37
37
  # Auto-generated display methods
38
38
  display_fields=[
39
- BadgeField(
40
- name="queue_name",
41
- title="Queue",
42
- variant="info",
43
- icon=Icons.LAYERS,
44
- ),
39
+ BadgeField(name="service_name", title="Service", variant="info", icon=Icons.API),
40
+ BadgeField(name="method_name", title="Method", variant="secondary", icon=Icons.CODE),
45
41
  BadgeField(
46
42
  name="status",
47
43
  title="Status",
48
44
  label_map={
49
- "queued": "secondary",
50
- "in_progress": "info",
51
- "completed": "success",
52
- "failed": "danger",
53
- "canceled": "warning",
45
+ "pending": "warning",
46
+ "success": "success",
47
+ "error": "danger",
48
+ "cancelled": "secondary",
49
+ "timeout": "danger",
54
50
  },
55
51
  ),
56
52
  UserField(name="user", title="User", header=True),
57
53
  DateTimeField(name="created_at", title="Created", ordering="created_at"),
58
- DateTimeField(name="started_at", title="Started", ordering="started_at"),
59
54
  DateTimeField(name="completed_at", title="Completed", ordering="completed_at"),
60
55
  ],
61
-
62
56
  # Filters
63
- list_filter=["status", "task_name", "queue_name", "created_at"],
57
+ list_filter=["status", "grpc_status_code", "service_name", "method_name", "is_authenticated", "created_at"],
64
58
  search_fields=[
65
- "job_id",
66
- "task_name",
67
- "worker_id",
68
- "error_message",
59
+ "request_id",
60
+ "service_name",
61
+ "method_name",
62
+ "full_method",
69
63
  "user__username",
70
64
  "user__email",
65
+ "error_message",
66
+ "client_ip",
71
67
  ],
72
-
73
68
  # Autocomplete for user field
74
69
  autocomplete_fields=["user"],
75
-
76
70
  # Readonly fields
77
71
  readonly_fields=[
78
72
  "id",
79
- "job_id",
73
+ "request_id",
80
74
  "created_at",
81
- "started_at",
82
75
  "completed_at",
83
- "duration_ms",
84
- "worker_id",
76
+ "request_data_display",
77
+ "response_data_display",
78
+ "error_details_display",
79
+ "performance_stats_display",
80
+ "client_info_display",
85
81
  ],
86
-
87
82
  # Date hierarchy
88
83
  date_hierarchy="created_at",
89
-
90
84
  # Per page
91
85
  list_per_page=50,
92
-
93
- # Ordering
94
- ordering=["-created_at"],
95
86
  )
96
87
 
97
88
 
98
- __all__ = ["tasklog_config"]
89
+ __all__ = ["grpcrequestlog_config"]
@@ -0,0 +1,252 @@
1
+ """
2
+ gRPC Request Log Admin.
3
+
4
+ PydanticAdmin for GRPCRequestLog 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 GRPCRequestLog
14
+ from .config import grpcrequestlog_config
15
+
16
+
17
+ @admin.register(GRPCRequestLog)
18
+ class GRPCRequestLogAdmin(PydanticAdmin):
19
+ """
20
+ gRPC request log admin with analytics and filtering.
21
+
22
+ Features:
23
+ - Color-coded status badges
24
+ - Performance metrics visualization
25
+ - Duration display with performance indicators
26
+ - Formatted JSON for request/response data
27
+ - Error details with highlighted display
28
+ """
29
+
30
+ config = grpcrequestlog_config
31
+
32
+ @computed_field("Service", ordering="service_name")
33
+ def service_badge(self, obj):
34
+ """Display service name as badge."""
35
+ return self.html.badge(obj.service_name, variant="info", icon=Icons.API)
36
+
37
+ @computed_field("Method", ordering="method_name")
38
+ def method_badge(self, obj):
39
+ """Display method name as badge."""
40
+ return self.html.badge(obj.method_name, variant="secondary", icon=Icons.CODE)
41
+
42
+ @computed_field("gRPC Status", ordering="grpc_status_code")
43
+ def grpc_status_code_display(self, obj):
44
+ """Display gRPC status code with color coding."""
45
+ if not obj.grpc_status_code:
46
+ return self.html.empty()
47
+
48
+ # Color code based on status
49
+ if obj.grpc_status_code == "OK":
50
+ variant = "success"
51
+ icon = Icons.CHECK_CIRCLE
52
+ elif obj.grpc_status_code in ["CANCELLED", "DEADLINE_EXCEEDED"]:
53
+ variant = "warning"
54
+ icon = Icons.TIMER
55
+ else:
56
+ variant = "danger"
57
+ icon = Icons.ERROR
58
+
59
+ return self.html.badge(obj.grpc_status_code, variant=variant, icon=icon)
60
+
61
+ @computed_field("Duration", ordering="duration_ms")
62
+ def duration_display(self, obj):
63
+ """Display duration with color coding based on speed."""
64
+ if obj.duration_ms is None:
65
+ return self.html.empty()
66
+
67
+ # Color code based on duration
68
+ if obj.duration_ms < 100:
69
+ variant = "success" # Fast
70
+ icon = Icons.SPEED
71
+ elif obj.duration_ms < 1000:
72
+ variant = "warning" # Moderate
73
+ icon = Icons.TIMER
74
+ else:
75
+ variant = "danger" # Slow
76
+ icon = Icons.ERROR
77
+
78
+ return self.html.badge(f"{obj.duration_ms}ms", variant=variant, icon=icon)
79
+
80
+ def request_data_display(self, obj):
81
+ """Display formatted JSON request data."""
82
+ if not obj.request_data:
83
+ return self.html.empty("No request data logged")
84
+
85
+ try:
86
+ formatted = json.dumps(obj.request_data, indent=2)
87
+ return self.html.code_block(formatted, language="json", max_height="400px")
88
+ except Exception:
89
+ return str(obj.request_data)
90
+
91
+ request_data_display.short_description = "Request Data"
92
+
93
+ def response_data_display(self, obj):
94
+ """Display formatted JSON response data."""
95
+ if not obj.response_data:
96
+ return self.html.empty("No response data logged")
97
+
98
+ try:
99
+ formatted = json.dumps(obj.response_data, indent=2)
100
+ return self.html.code_block(formatted, language="json", max_height="400px")
101
+ except Exception:
102
+ return str(obj.response_data)
103
+
104
+ response_data_display.short_description = "Response Data"
105
+
106
+ def error_details_display(self, obj):
107
+ """Display error information if request failed."""
108
+ if obj.is_successful or obj.status == "pending":
109
+ return self.html.inline(
110
+ self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
111
+ self.html.text("No errors", variant="success"),
112
+ separator=" "
113
+ )
114
+
115
+ # gRPC status code
116
+ code_line = self.html.key_value(
117
+ "gRPC Status",
118
+ self.html.badge(obj.grpc_status_code, variant="danger", icon=Icons.ERROR)
119
+ ) if obj.grpc_status_code else None
120
+
121
+ # Error message
122
+ msg_line = self.html.key_value(
123
+ "Message",
124
+ self.html.text(obj.error_message, variant="danger")
125
+ ) if obj.error_message else None
126
+
127
+ # Error details
128
+ details_line = None
129
+ if obj.error_details:
130
+ try:
131
+ formatted = json.dumps(obj.error_details, indent=2)
132
+ details_line = self.html.key_value(
133
+ "Details",
134
+ self.html.code_block(formatted, language="json", max_height="200px")
135
+ )
136
+ except Exception:
137
+ pass
138
+
139
+ return self.html.breakdown(code_line, msg_line, details_line) if (code_line or msg_line) else self.html.empty()
140
+
141
+ error_details_display.short_description = "Error Details"
142
+
143
+ def performance_stats_display(self, obj):
144
+ """Display performance statistics."""
145
+ # Duration
146
+ duration_line = self.html.key_value(
147
+ "Duration",
148
+ self.html.number(obj.duration_ms, suffix="ms") if obj.duration_ms else "N/A"
149
+ )
150
+
151
+ # Request size
152
+ request_size_line = self.html.key_value(
153
+ "Request Size",
154
+ self.html.number(obj.request_size, suffix=" bytes") if obj.request_size else "N/A"
155
+ )
156
+
157
+ # Response size
158
+ response_size_line = self.html.key_value(
159
+ "Response Size",
160
+ self.html.number(obj.response_size, suffix=" bytes") if obj.response_size else "N/A"
161
+ )
162
+
163
+ # Authentication
164
+ auth_line = self.html.key_value(
165
+ "Authenticated",
166
+ self.html.badge("Yes" if obj.is_authenticated else "No",
167
+ variant="success" if obj.is_authenticated else "secondary")
168
+ )
169
+
170
+ return self.html.breakdown(duration_line, request_size_line, response_size_line, auth_line)
171
+
172
+ performance_stats_display.short_description = "Performance Statistics"
173
+
174
+ def client_info_display(self, obj):
175
+ """Display client information."""
176
+ # Client IP
177
+ ip_line = self.html.key_value(
178
+ "Client IP",
179
+ obj.client_ip if obj.client_ip else "N/A"
180
+ )
181
+
182
+ # User Agent
183
+ ua_line = self.html.key_value(
184
+ "User Agent",
185
+ obj.user_agent if obj.user_agent else "N/A"
186
+ )
187
+
188
+ # Peer
189
+ peer_line = self.html.key_value(
190
+ "Peer",
191
+ self.html.text(obj.peer, variant="secondary") if obj.peer else "N/A"
192
+ )
193
+
194
+ return self.html.breakdown(ip_line, ua_line, peer_line)
195
+
196
+ client_info_display.short_description = "Client Information"
197
+
198
+ # Fieldsets for detail view
199
+ def get_fieldsets(self, request, obj=None):
200
+ """Dynamic fieldsets based on object state."""
201
+ fieldsets = [
202
+ (
203
+ "Request Information",
204
+ {"fields": ("id", "request_id", "full_method", "service_name", "method_name", "status")},
205
+ ),
206
+ (
207
+ "User Context",
208
+ {"fields": ("user", "is_authenticated")},
209
+ ),
210
+ (
211
+ "Performance",
212
+ {"fields": ("performance_stats_display", "duration_ms", "created_at", "completed_at")},
213
+ ),
214
+ (
215
+ "Client Information",
216
+ {"fields": ("client_info_display", "client_ip", "user_agent", "peer"), "classes": ("collapse",)},
217
+ ),
218
+ ]
219
+
220
+ # Add request/response data sections if available
221
+ if obj and obj.request_data:
222
+ fieldsets.insert(
223
+ 3,
224
+ (
225
+ "Request Data",
226
+ {"fields": ("request_data_display",), "classes": ("collapse",)},
227
+ ),
228
+ )
229
+
230
+ if obj and obj.response_data:
231
+ fieldsets.insert(
232
+ 4,
233
+ (
234
+ "Response Data",
235
+ {"fields": ("response_data_display",), "classes": ("collapse",)},
236
+ ),
237
+ )
238
+
239
+ # Add error section only if failed
240
+ if obj and not obj.is_successful and obj.status != "pending":
241
+ fieldsets.insert(
242
+ 5,
243
+ (
244
+ "Error Details",
245
+ {"fields": ("error_details_display", "grpc_status_code", "error_message", "error_details")},
246
+ ),
247
+ )
248
+
249
+ return fieldsets
250
+
251
+
252
+ __all__ = ["GRPCRequestLogAdmin"]
@@ -0,0 +1,28 @@
1
+ """
2
+ Django app configuration for gRPC integration.
3
+ """
4
+
5
+ from django.apps import AppConfig
6
+
7
+
8
+ class GRPCAppConfig(AppConfig):
9
+ """
10
+ Django app config for gRPC integration.
11
+
12
+ Provides:
13
+ - gRPC server with Django ORM integration
14
+ - JWT authentication
15
+ - Request logging to database
16
+ - Admin interface for monitoring
17
+ - REST API for metrics
18
+ """
19
+
20
+ default_auto_field = 'django.db.models.BigAutoField'
21
+ name = 'django_cfg.apps.grpc'
22
+ verbose_name = 'gRPC Integration'
23
+
24
+ def ready(self):
25
+ """Called when Django starts."""
26
+ # Import signal handlers if needed
27
+ # from . import signals
28
+ pass
@@ -0,0 +1,9 @@
1
+ """
2
+ gRPC authentication components.
3
+
4
+ Provides JWT authentication for gRPC services.
5
+ """
6
+
7
+ from .jwt_auth import JWTAuthInterceptor
8
+
9
+ __all__ = ["JWTAuthInterceptor"]