django-cfg 1.5.8__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 (159) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/commands/serializers.py +152 -0
  3. django_cfg/apps/api/commands/views.py +32 -0
  4. django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
  5. django_cfg/apps/business/accounts/serializers/profile.py +42 -0
  6. django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
  7. django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
  8. django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
  9. django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
  10. django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
  11. django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
  12. django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
  13. django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
  14. django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
  15. django_cfg/apps/business/support/serializers.py +3 -2
  16. django_cfg/apps/integrations/centrifugo/apps.py +2 -1
  17. django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
  18. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
  19. django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
  20. django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
  21. django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
  22. django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
  23. django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
  24. django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
  25. django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
  26. django_cfg/apps/integrations/centrifugo/urls.py +8 -0
  27. django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
  28. django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
  29. django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
  30. django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
  31. django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
  32. django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
  33. django_cfg/apps/integrations/grpc/admin/config.py +113 -9
  34. django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
  35. django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
  36. django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
  37. django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
  38. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
  39. django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
  40. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
  41. django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
  42. django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
  43. django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
  44. django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
  45. django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
  46. django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
  47. django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
  48. django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
  49. django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
  50. django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
  51. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
  52. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
  53. django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
  54. django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
  55. django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
  56. django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
  57. django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
  58. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
  59. django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
  60. django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
  61. django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
  62. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
  63. django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
  64. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
  65. django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
  66. django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
  67. django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
  68. django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
  69. django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
  70. django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
  71. django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
  72. django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
  73. django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
  74. django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
  75. django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
  76. django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
  77. django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
  78. django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
  79. django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
  80. django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
  81. django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
  82. django_cfg/apps/integrations/grpc/urls.py +8 -0
  83. django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
  84. django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
  85. django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
  86. django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
  87. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
  88. django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
  89. django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
  90. django_cfg/apps/integrations/grpc/views/charts.py +21 -14
  91. django_cfg/apps/integrations/grpc/views/config.py +8 -6
  92. django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
  93. django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
  94. django_cfg/apps/integrations/grpc/views/services.py +30 -21
  95. django_cfg/apps/integrations/grpc/views/testing.py +45 -43
  96. django_cfg/apps/integrations/rq/views/jobs.py +19 -9
  97. django_cfg/apps/integrations/rq/views/schedule.py +7 -3
  98. django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
  99. django_cfg/apps/system/dashboard/serializers/config.py +95 -9
  100. django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
  101. django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
  102. django_cfg/apps/system/frontend/views.py +87 -6
  103. django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
  104. django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
  105. django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
  106. django_cfg/config.py +33 -0
  107. django_cfg/core/builders/security_builder.py +1 -0
  108. django_cfg/core/generation/integration_generators/api.py +2 -0
  109. django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
  110. django_cfg/management/commands/check_endpoints.py +2 -2
  111. django_cfg/management/commands/check_settings.py +3 -10
  112. django_cfg/management/commands/clear_constance.py +3 -10
  113. django_cfg/management/commands/create_token.py +4 -11
  114. django_cfg/management/commands/list_urls.py +4 -10
  115. django_cfg/management/commands/migrate_all.py +18 -12
  116. django_cfg/management/commands/migrator.py +4 -11
  117. django_cfg/management/commands/script.py +4 -10
  118. django_cfg/management/commands/show_config.py +8 -16
  119. django_cfg/management/commands/show_urls.py +5 -11
  120. django_cfg/management/commands/superuser.py +4 -11
  121. django_cfg/management/commands/tree.py +5 -10
  122. django_cfg/management/utils/README.md +402 -0
  123. django_cfg/management/utils/__init__.py +29 -0
  124. django_cfg/management/utils/mixins.py +176 -0
  125. django_cfg/middleware/pagination.py +53 -54
  126. django_cfg/models/api/grpc/__init__.py +15 -21
  127. django_cfg/models/api/grpc/config.py +155 -73
  128. django_cfg/models/ngrok/config.py +7 -6
  129. django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
  130. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
  131. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
  132. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
  133. django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
  134. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
  135. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
  136. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
  137. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
  138. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
  139. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
  140. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
  141. django_cfg/modules/django_client/core/ir/schema.py +15 -1
  142. django_cfg/modules/django_client/core/parser/base.py +126 -30
  143. django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
  144. django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
  145. django_cfg/modules/django_email/management/commands/test_email.py +4 -10
  146. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
  147. django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
  148. django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
  149. django_cfg/modules/django_unfold/navigation.py +6 -18
  150. django_cfg/pyproject.toml +1 -1
  151. django_cfg/registry/modules.py +1 -4
  152. django_cfg/requirements.txt +52 -0
  153. django_cfg/static/frontend/admin.zip +0 -0
  154. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
  155. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
  156. django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
  157. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
  158. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
  159. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
@@ -72,8 +72,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
72
72
  try:
73
73
  hours = self._validate_hours(request.GET.get("hours", "24"))
74
74
  data = ChartGeneratorService.generate_server_uptime_data(hours)
75
- serializer = ServerUptimeChartSerializer(**data)
76
- return Response(serializer.model_dump())
75
+ serializer = ServerUptimeChartSerializer(data=data)
76
+ serializer.is_valid(raise_exception=True)
77
+ return Response(serializer.data)
77
78
  except Exception as e:
78
79
  logger.error(f"Server uptime chart error: {e}", exc_info=True)
79
80
  return Response(
@@ -94,8 +95,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
94
95
  try:
95
96
  hours = self._validate_hours(request.GET.get("hours", "24"))
96
97
  data = ChartGeneratorService.generate_request_volume_data(hours)
97
- serializer = RequestVolumeChartSerializer(**data)
98
- return Response(serializer.model_dump())
98
+ serializer = RequestVolumeChartSerializer(data=data)
99
+ serializer.is_valid(raise_exception=True)
100
+ return Response(serializer.data)
99
101
  except Exception as e:
100
102
  logger.error(f"Request volume chart error: {e}", exc_info=True)
101
103
  return Response(
@@ -116,8 +118,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
116
118
  try:
117
119
  hours = self._validate_hours(request.GET.get("hours", "24"))
118
120
  data = ChartGeneratorService.generate_response_time_data(hours)
119
- serializer = ResponseTimeChartSerializer(**data)
120
- return Response(serializer.model_dump())
121
+ serializer = ResponseTimeChartSerializer(data=data)
122
+ serializer.is_valid(raise_exception=True)
123
+ return Response(serializer.data)
121
124
  except Exception as e:
122
125
  logger.error(f"Response time chart error: {e}", exc_info=True)
123
126
  return Response(
@@ -138,8 +141,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
138
141
  try:
139
142
  hours = self._validate_hours(request.GET.get("hours", "24"))
140
143
  data = ChartGeneratorService.generate_service_activity_data(hours)
141
- serializer = ServiceActivityChartSerializer(**data)
142
- return Response(serializer.model_dump())
144
+ serializer = ServiceActivityChartSerializer(data=data)
145
+ serializer.is_valid(raise_exception=True)
146
+ return Response(serializer.data)
143
147
  except Exception as e:
144
148
  logger.error(f"Service activity chart error: {e}", exc_info=True)
145
149
  return Response(
@@ -160,8 +164,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
160
164
  try:
161
165
  hours = self._validate_hours(request.GET.get("hours", "24"))
162
166
  data = ChartGeneratorService.generate_server_lifecycle_data(hours)
163
- serializer = ServerLifecycleChartSerializer(**data)
164
- return Response(serializer.model_dump())
167
+ serializer = ServerLifecycleChartSerializer(data=data)
168
+ serializer.is_valid(raise_exception=True)
169
+ return Response(serializer.data)
165
170
  except Exception as e:
166
171
  logger.error(f"Server lifecycle chart error: {e}", exc_info=True)
167
172
  return Response(
@@ -182,8 +187,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
182
187
  try:
183
188
  hours = self._validate_hours(request.GET.get("hours", "24"))
184
189
  data = ChartGeneratorService.generate_error_distribution_data(hours)
185
- serializer = ErrorDistributionChartSerializer(**data)
186
- return Response(serializer.model_dump())
190
+ serializer = ErrorDistributionChartSerializer(data=data)
191
+ serializer.is_valid(raise_exception=True)
192
+ return Response(serializer.data)
187
193
  except Exception as e:
188
194
  logger.error(f"Error distribution chart error: {e}", exc_info=True)
189
195
  return Response(
@@ -204,8 +210,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
204
210
  try:
205
211
  hours = self._validate_hours(request.GET.get("hours", "24"))
206
212
  data = ChartGeneratorService.generate_dashboard_data(hours)
207
- serializer = DashboardChartsSerializer(**data)
208
- return Response(serializer.model_dump())
213
+ serializer = DashboardChartsSerializer(data=data)
214
+ serializer.is_valid(raise_exception=True)
215
+ return Response(serializer.data)
209
216
  except Exception as e:
210
217
  logger.error(f"Dashboard charts error: {e}", exc_info=True)
211
218
  return Response(
@@ -67,7 +67,7 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
67
67
  "host": grpc_config.server.host,
68
68
  "port": grpc_config.server.port,
69
69
  "enabled": grpc_config.server.enabled,
70
- "max_workers": grpc_config.server.max_workers,
70
+ "max_concurrent_streams": grpc_config.server.max_concurrent_streams,
71
71
  "max_concurrent_rpcs": None, # Not in current config
72
72
  },
73
73
  "framework": {
@@ -77,7 +77,7 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
77
77
  "interceptors": grpc_config.server.interceptors,
78
78
  },
79
79
  "features": {
80
- "jwt_auth": grpc_config.auth.enabled,
80
+ "api_key_auth": grpc_config.auth.enabled,
81
81
  "request_logging": True, # Always on
82
82
  "metrics": True, # Always on
83
83
  "reflection": grpc_config.server.enable_reflection,
@@ -86,8 +86,9 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
86
86
  "total_methods": methods_count,
87
87
  }
88
88
 
89
- serializer = GRPCConfigSerializer(**config_data)
90
- return Response(serializer.model_dump())
89
+ serializer = GRPCConfigSerializer(data=config_data)
90
+ serializer.is_valid(raise_exception=True)
91
+ return Response(serializer.data)
91
92
 
92
93
  except Exception as e:
93
94
  logger.error(f"Config fetch error: {e}", exc_info=True)
@@ -177,8 +178,9 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
177
178
  },
178
179
  }
179
180
 
180
- serializer = GRPCServerInfoSerializer(**server_info_data)
181
- return Response(serializer.model_dump())
181
+ serializer = GRPCServerInfoSerializer(data=server_info_data)
182
+ serializer.is_valid(raise_exception=True)
183
+ return Response(serializer.data)
182
184
 
183
185
  except Exception as e:
184
186
  logger.error(f"Server info error: {e}", exc_info=True)
@@ -11,6 +11,7 @@ from django.db import models
11
11
  from django.db.models import Avg, Count, Max
12
12
  from django.db.models.functions import TruncDay, TruncHour
13
13
  from django_cfg.mixins import AdminAPIMixin
14
+ from django_cfg.middleware.pagination import DefaultPagination
14
15
  from django_cfg.modules.django_logging import get_logger
15
16
  from drf_spectacular.types import OpenApiTypes
16
17
  from drf_spectacular.utils import OpenApiParameter, extend_schema
@@ -24,8 +25,6 @@ from ..serializers import (
24
25
  GRPCOverviewStatsSerializer,
25
26
  MethodListSerializer,
26
27
  MethodStatsSerializer,
27
- MonitoringServiceStatsSerializer,
28
- ServiceListSerializer,
29
28
  )
30
29
  from ..serializers.service_registry import RecentRequestSerializer
31
30
  from ..services import MonitoringService
@@ -48,6 +47,8 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
48
47
  Requires admin authentication (JWT, Session, or Basic Auth).
49
48
  """
50
49
 
50
+ pagination_class = DefaultPagination
51
+
51
52
  # Required for GenericViewSet
52
53
  queryset = GRPCRequestLog.objects.none() # Placeholder, actual queries in actions
53
54
  serializer_class = RecentRequestSerializer # Default serializer for schema
@@ -67,7 +68,9 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
67
68
  try:
68
69
  service = MonitoringService()
69
70
  health_data = service.get_health_status()
70
- return Response(health_data)
71
+ serializer = GRPCHealthCheckSerializer(data=health_data)
72
+ serializer.is_valid(raise_exception=True)
73
+ return Response(serializer.data)
71
74
 
72
75
  except ValueError as e:
73
76
  return Response(
@@ -107,7 +110,9 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
107
110
 
108
111
  service = MonitoringService()
109
112
  stats = service.get_overview_statistics(hours=hours)
110
- return Response(stats)
113
+ serializer = GRPCOverviewStatsSerializer(data=stats)
114
+ serializer.is_valid(raise_exception=True)
115
+ return Response(serializer.data)
111
116
 
112
117
  except ValueError as e:
113
118
  logger.warning(f"Overview stats validation error: {e}")
@@ -149,7 +154,7 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
149
154
  ),
150
155
  ],
151
156
  responses={
152
- 200: RecentRequestSerializer,
157
+ 200: RecentRequestSerializer(many=True), # Use many=True for paginated list
153
158
  400: {"description": "Invalid parameters"},
154
159
  },
155
160
  )
@@ -174,37 +179,49 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
174
179
  # Serialize paginated data
175
180
  requests_list = []
176
181
  for req in page:
177
- req_serializer = RecentRequestSerializer(
178
- id=req.id,
179
- request_id=req.request_id,
180
- service_name=req.service_name,
181
- method_name=req.method_name,
182
- status=req.status,
183
- duration_ms=req.duration_ms or 0,
184
- grpc_status_code=req.grpc_status_code or "",
185
- error_message=req.error_message or "",
186
- created_at=req.created_at.isoformat(),
187
- client_ip=req.client_ip or "",
188
- )
189
- requests_list.append(req_serializer.model_dump())
182
+ requests_list.append({
183
+ "id": req.id,
184
+ "request_id": req.request_id,
185
+ "service_name": req.service_name,
186
+ "method_name": req.method_name,
187
+ "status": req.status,
188
+ "duration_ms": req.duration_ms or 0,
189
+ "grpc_status_code": req.grpc_status_code or "",
190
+ "error_message": req.error_message or "",
191
+ "created_at": req.created_at.isoformat(),
192
+ "client_ip": req.client_ip or "",
193
+ # User information
194
+ "user_id": req.user.id if req.user else None,
195
+ "username": req.user.username if req.user else "",
196
+ "is_authenticated": req.is_authenticated,
197
+ # API Key information
198
+ "api_key_id": req.api_key.id if req.api_key else None,
199
+ "api_key_name": req.api_key.name if req.api_key else "",
200
+ })
190
201
  return self.get_paginated_response(requests_list)
191
202
 
192
203
  # No pagination fallback
193
204
  requests_list = []
194
205
  for req in queryset[:100]:
195
- req_serializer = RecentRequestSerializer(
196
- id=req.id,
197
- request_id=req.request_id,
198
- service_name=req.service_name,
199
- method_name=req.method_name,
200
- status=req.status,
201
- duration_ms=req.duration_ms or 0,
202
- grpc_status_code=req.grpc_status_code or "",
203
- error_message=req.error_message or "",
204
- created_at=req.created_at.isoformat(),
205
- client_ip=req.client_ip or "",
206
- )
207
- requests_list.append(req_serializer.model_dump())
206
+ requests_list.append({
207
+ "id": req.id,
208
+ "request_id": req.request_id,
209
+ "service_name": req.service_name,
210
+ "method_name": req.method_name,
211
+ "status": req.status,
212
+ "duration_ms": req.duration_ms or 0,
213
+ "grpc_status_code": req.grpc_status_code or "",
214
+ "error_message": req.error_message or "",
215
+ "created_at": req.created_at.isoformat(),
216
+ "client_ip": req.client_ip or "",
217
+ # User information
218
+ "user_id": req.user.id if req.user else None,
219
+ "username": req.user.username if req.user else "",
220
+ "is_authenticated": req.is_authenticated,
221
+ # API Key information
222
+ "api_key_id": req.api_key.id if req.api_key else None,
223
+ "api_key_name": req.api_key.name if req.api_key else "",
224
+ })
208
225
  return Response({"requests": requests_list, "count": len(requests_list)})
209
226
 
210
227
  except ValueError as e:
@@ -219,52 +236,6 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
219
236
  status=status.HTTP_500_INTERNAL_SERVER_ERROR,
220
237
  )
221
238
 
222
- @extend_schema(
223
- tags=["gRPC Monitoring"],
224
- summary="Get service statistics",
225
- description="Returns statistics grouped by service.",
226
- parameters=[
227
- OpenApiParameter(
228
- name="hours",
229
- type=OpenApiTypes.INT,
230
- location=OpenApiParameter.QUERY,
231
- description="Statistics period in hours (default: 24)",
232
- required=False,
233
- ),
234
- ],
235
- responses={
236
- 200: ServiceListSerializer,
237
- 400: {"description": "Invalid parameters"},
238
- },
239
- )
240
- @action(detail=False, methods=["get"], url_path="services", pagination_class=None)
241
- def services(self, request):
242
- """Get statistics per service."""
243
- try:
244
- hours = int(request.GET.get("hours", 24))
245
-
246
- service = MonitoringService()
247
- services_list = service.get_service_statistics(hours=hours)
248
-
249
- response_data = {
250
- "services": services_list,
251
- "total_services": len(services_list),
252
- }
253
-
254
- return Response(response_data)
255
-
256
- except ValueError as e:
257
- logger.warning(f"Service stats validation error: {e}")
258
- return Response(
259
- {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
260
- )
261
- except Exception as e:
262
- logger.error(f"Service stats error: {e}", exc_info=True)
263
- return Response(
264
- {"error": "Internal server error"},
265
- status=status.HTTP_500_INTERNAL_SERVER_ERROR,
266
- )
267
-
268
239
  @extend_schema(
269
240
  tags=["gRPC Monitoring"],
270
241
  summary="Get method statistics",
@@ -308,8 +279,9 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
308
279
  "total_methods": len(methods_list),
309
280
  }
310
281
 
311
- serializer = MethodListSerializer(**response_data)
312
- return Response(serializer.model_dump())
282
+ serializer = MethodListSerializer(data=response_data)
283
+ serializer.is_valid(raise_exception=True)
284
+ return Response(serializer.data)
313
285
 
314
286
  except ValueError as e:
315
287
  logger.warning(f"Method stats validation error: {e}")
@@ -0,0 +1,214 @@
1
+ """
2
+ gRPC Proto Files ViewSet.
3
+
4
+ Provides REST API endpoints for downloading proto files generated from Django models.
5
+ """
6
+
7
+ from django.http import FileResponse, HttpResponse
8
+ from django_cfg.mixins import AdminAPIMixin
9
+ from django_cfg.modules.django_logging import get_logger
10
+ from drf_spectacular.types import OpenApiTypes
11
+ from drf_spectacular.utils import OpenApiParameter, extend_schema
12
+ from rest_framework import status, viewsets
13
+ from rest_framework.decorators import action
14
+ from rest_framework.response import Response
15
+
16
+ from ..serializers.proto_files import (
17
+ ProtoFileListSerializer,
18
+ ProtoGenerateRequestSerializer,
19
+ ProtoGenerateResponseSerializer,
20
+ )
21
+ from ..services import ProtoFilesManager
22
+
23
+ logger = get_logger("grpc.proto_files")
24
+
25
+
26
+ class GRPCProtoFilesViewSet(AdminAPIMixin, viewsets.ViewSet):
27
+ """
28
+ ViewSet for gRPC proto files management.
29
+
30
+ Provides endpoints for:
31
+ - List all available proto files
32
+ - Download specific proto file
33
+ - Download all proto files as .zip
34
+ - Trigger proto generation
35
+
36
+ Requires admin authentication (JWT, Session, or Basic Auth).
37
+ """
38
+
39
+ lookup_value_regex = r'[^/]+'
40
+
41
+ def __init__(self, **kwargs):
42
+ super().__init__(**kwargs)
43
+ self.manager = ProtoFilesManager()
44
+
45
+ @extend_schema(
46
+ tags=["gRPC Proto Files"],
47
+ summary="List all proto files",
48
+ description="Returns list of all available proto files with metadata.",
49
+ responses={
50
+ 200: ProtoFileListSerializer,
51
+ },
52
+ )
53
+ def list(self, request):
54
+ """List all available proto files."""
55
+ try:
56
+ from django.urls import reverse
57
+ from django_cfg.core.state import get_current_config
58
+
59
+ proto_files = self.manager.scan_proto_files(request=request)
60
+
61
+ config = get_current_config()
62
+
63
+ # Build download-all URL
64
+ # Use api_url from config (respects HTTPS behind reverse proxy)
65
+ # Falls back to request.build_absolute_uri if config not available
66
+ if config and hasattr(config, 'api_url'):
67
+ path = reverse('django_cfg_grpc:proto-files-download-all')
68
+ download_all_url = f"{config.api_url}{path}"
69
+ else:
70
+ download_all_url = request.build_absolute_uri(
71
+ reverse('django_cfg_grpc:proto-files-download-all')
72
+ )
73
+
74
+ response_data = {
75
+ "files": proto_files,
76
+ "total_files": len(proto_files),
77
+ "proto_dir": str(self.manager.get_proto_dir()),
78
+ "download_all_url": download_all_url,
79
+ }
80
+
81
+ serializer = ProtoFileListSerializer(data=response_data)
82
+ serializer.is_valid(raise_exception=True)
83
+ return Response(serializer.data)
84
+
85
+ except Exception as e:
86
+ logger.error(f"Proto files list error: {e}", exc_info=True)
87
+ return Response(
88
+ {"error": "Internal server error"},
89
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
90
+ )
91
+
92
+ @extend_schema(
93
+ tags=["gRPC Proto Files"],
94
+ summary="Download proto file",
95
+ description="Download specific proto file by app label.",
96
+ parameters=[
97
+ OpenApiParameter(
98
+ name="pk",
99
+ type=OpenApiTypes.STR,
100
+ location=OpenApiParameter.PATH,
101
+ description="App label (e.g., 'crypto')",
102
+ required=True,
103
+ ),
104
+ ],
105
+ responses={
106
+ 200: {
107
+ "description": "Proto file content",
108
+ "content": {"text/plain": {}},
109
+ },
110
+ 404: {"description": "Proto file not found"},
111
+ },
112
+ )
113
+ def retrieve(self, request, pk=None):
114
+ """Download specific proto file."""
115
+ try:
116
+ app_label = pk
117
+ proto_file = self.manager.get_proto_file(app_label)
118
+
119
+ if not proto_file:
120
+ return Response(
121
+ {"error": f"Proto file for app '{app_label}' not found"},
122
+ status=status.HTTP_404_NOT_FOUND,
123
+ )
124
+
125
+ # Return proto file content
126
+ response = FileResponse(
127
+ open(proto_file, "rb"),
128
+ content_type="text/plain; charset=utf-8",
129
+ )
130
+ response["Content-Disposition"] = f'attachment; filename="{proto_file.name}"'
131
+ return response
132
+
133
+ except Exception as e:
134
+ logger.error(f"Proto file download error: {e}", exc_info=True)
135
+ return Response(
136
+ {"error": "Internal server error"},
137
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
138
+ )
139
+
140
+ @extend_schema(
141
+ tags=["gRPC Proto Files"],
142
+ summary="Download all proto files",
143
+ description="Download all proto files as a .zip archive.",
144
+ responses={
145
+ 200: {
146
+ "description": "Zip archive with all proto files",
147
+ "content": {"application/zip": {}},
148
+ },
149
+ 404: {"description": "No proto files found"},
150
+ },
151
+ )
152
+ @action(detail=False, methods=["get"], url_path="download-all")
153
+ def download_all(self, request):
154
+ """Download all proto files as .zip archive."""
155
+ try:
156
+ zip_data = self.manager.create_zip_archive()
157
+
158
+ if not zip_data:
159
+ return Response(
160
+ {"error": "No proto files found"},
161
+ status=status.HTTP_404_NOT_FOUND,
162
+ )
163
+
164
+ # Return zip file
165
+ response = HttpResponse(zip_data, content_type="application/zip")
166
+ response["Content-Disposition"] = 'attachment; filename="protos.zip"'
167
+ return response
168
+
169
+ except Exception as e:
170
+ logger.error(f"Proto files zip error: {e}", exc_info=True)
171
+ return Response(
172
+ {"error": "Internal server error"},
173
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
174
+ )
175
+
176
+ @extend_schema(
177
+ tags=["gRPC Proto Files"],
178
+ summary="Generate proto files",
179
+ description="Trigger proto file generation for specified apps.",
180
+ request=ProtoGenerateRequestSerializer,
181
+ responses={
182
+ 200: ProtoGenerateResponseSerializer,
183
+ 400: {"description": "Bad request"},
184
+ },
185
+ )
186
+ @action(detail=False, methods=["post"], url_path="generate")
187
+ def generate(self, request):
188
+ """Trigger proto generation for specified apps."""
189
+ try:
190
+ serializer = ProtoGenerateRequestSerializer(data=request.data)
191
+ serializer.is_valid(raise_exception=True)
192
+
193
+ apps = serializer.validated_data.get("apps")
194
+ force = serializer.validated_data.get("force", False)
195
+
196
+ # Generate protos via service
197
+ result = self.manager.generate_protos(apps=apps, force=force)
198
+
199
+ # Add proto_dir to response
200
+ result["proto_dir"] = str(self.manager.get_proto_dir())
201
+
202
+ response_serializer = ProtoGenerateResponseSerializer(data=result)
203
+ response_serializer.is_valid(raise_exception=True)
204
+ return Response(response_serializer.data)
205
+
206
+ except Exception as e:
207
+ logger.error(f"Proto generation error: {e}", exc_info=True)
208
+ return Response(
209
+ {"error": "Internal server error"},
210
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
211
+ )
212
+
213
+
214
+ __all__ = ["GRPCProtoFilesViewSet"]
@@ -6,6 +6,7 @@ Provides REST API endpoints for viewing registered gRPC services and their metho
6
6
 
7
7
  from django.db import models
8
8
  from django.db.models import Avg, Count, Max, Min
9
+ from django_cfg.middleware.pagination import DefaultPagination
9
10
  from django_cfg.mixins import AdminAPIMixin
10
11
  from django_cfg.modules.django_logging import get_logger
11
12
  from drf_spectacular.types import OpenApiTypes
@@ -20,13 +21,14 @@ from ..serializers.service_registry import (
20
21
  ServiceDetailSerializer,
21
22
  ServiceListSerializer,
22
23
  ServiceMethodsSerializer,
24
+ ServiceSummarySerializer,
23
25
  )
24
26
  from ..services import ServiceRegistryManager
25
27
 
26
28
  logger = get_logger("grpc.services")
27
29
 
28
30
 
29
- class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
31
+ class GRPCServiceViewSet(AdminAPIMixin, viewsets.GenericViewSet):
30
32
  """
31
33
  ViewSet for gRPC service registry and management.
32
34
 
@@ -39,13 +41,20 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
39
41
  Requires admin authentication (JWT, Session, or Basic Auth).
40
42
  """
41
43
 
44
+ # Pagination for list endpoint
45
+ pagination_class = DefaultPagination
46
+
47
+ # Required for GenericViewSet
48
+ queryset = GRPCRequestLog.objects.none() # Placeholder
49
+ serializer_class = ServiceSummarySerializer
50
+
42
51
  # Allow dots in service names (e.g., apps.CryptoService)
43
52
  lookup_value_regex = r'[^/]+'
44
53
 
45
54
  @extend_schema(
46
55
  tags=["gRPC Services"],
47
56
  summary="List all services",
48
- description="Returns list of all registered gRPC services with basic statistics.",
57
+ description="Returns paginated list of all registered gRPC services with basic statistics.",
49
58
  parameters=[
50
59
  OpenApiParameter(
51
60
  name="hours",
@@ -54,13 +63,14 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
54
63
  description="Statistics period in hours (default: 24)",
55
64
  required=False,
56
65
  ),
66
+ # Note: page, page_size added automatically by pagination_class
57
67
  ],
58
68
  responses={
59
- 200: ServiceListSerializer,
69
+ 200: ServiceSummarySerializer(many=True),
60
70
  },
61
71
  )
62
72
  def list(self, request):
63
- """List all registered gRPC services."""
73
+ """List all registered gRPC services with pagination."""
64
74
  try:
65
75
  hours = int(request.GET.get("hours", 24))
66
76
  hours = min(max(hours, 1), 168)
@@ -68,23 +78,20 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
68
78
  # Use service registry manager
69
79
  registry = ServiceRegistryManager()
70
80
 
71
- if not registry.is_server_running():
72
- # No running server - return empty list
73
- return Response({
74
- "services": [],
75
- "total_services": 0,
76
- })
77
-
78
81
  # Get services with stats from service layer
82
+ # Note: This will return empty list if server is not running,
83
+ # but that's expected - services are only known when server is started
79
84
  services_list = registry.get_all_services_with_stats(hours=hours)
80
85
 
81
- response_data = {
82
- "services": services_list,
83
- "total_services": len(services_list),
84
- }
86
+ # Paginate the services list (works with empty list too)
87
+ page = self.paginate_queryset(services_list)
88
+ if page is not None:
89
+ serializer = ServiceSummarySerializer(page, many=True)
90
+ return self.get_paginated_response(serializer.data)
85
91
 
86
- serializer = ServiceListSerializer(**response_data)
87
- return Response(serializer.model_dump())
92
+ # Fallback (no pagination)
93
+ serializer = ServiceSummarySerializer(services_list, many=True)
94
+ return Response(serializer.data)
88
95
 
89
96
  except Exception as e:
90
97
  logger.error(f"Service list error: {e}", exc_info=True)
@@ -216,8 +223,9 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
216
223
  ],
217
224
  }
218
225
 
219
- serializer = ServiceDetailSerializer(**service_detail)
220
- return Response(serializer.model_dump())
226
+ serializer = ServiceDetailSerializer(data=service_detail)
227
+ serializer.is_valid(raise_exception=True)
228
+ return Response(serializer.data)
221
229
 
222
230
  except Exception as e:
223
231
  logger.error(f"Service detail error: {e}", exc_info=True)
@@ -275,8 +283,9 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
275
283
  "total_methods": len(methods_list),
276
284
  }
277
285
 
278
- serializer = ServiceMethodsSerializer(**response_data)
279
- return Response(serializer.model_dump())
286
+ serializer = ServiceMethodsSerializer(data=response_data)
287
+ serializer.is_valid(raise_exception=True)
288
+ return Response(serializer.data)
280
289
 
281
290
  except Exception as e:
282
291
  logger.error(f"Service methods error: {e}", exc_info=True)