django-cfg 1.4.119__py3-none-any.whl → 1.5.1__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.
- django_cfg/__init__.py +8 -4
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
- django_cfg/apps/grpc/__init__.py +9 -0
- django_cfg/apps/grpc/admin/__init__.py +11 -0
- django_cfg/apps/grpc/admin/config.py +89 -0
- django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
- django_cfg/apps/grpc/apps.py +28 -0
- django_cfg/apps/grpc/auth/__init__.py +9 -0
- django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
- django_cfg/apps/grpc/interceptors/__init__.py +19 -0
- django_cfg/apps/grpc/interceptors/errors.py +241 -0
- django_cfg/apps/grpc/interceptors/logging.py +270 -0
- django_cfg/apps/grpc/interceptors/metrics.py +306 -0
- django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
- django_cfg/apps/grpc/management/__init__.py +1 -0
- django_cfg/apps/grpc/management/commands/__init__.py +0 -0
- django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
- django_cfg/apps/grpc/managers/__init__.py +10 -0
- django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
- django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
- django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
- django_cfg/apps/grpc/migrations/__init__.py +0 -0
- django_cfg/apps/grpc/models/__init__.py +9 -0
- django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
- django_cfg/apps/grpc/serializers/__init__.py +23 -0
- django_cfg/apps/grpc/serializers/health.py +18 -0
- django_cfg/apps/grpc/serializers/requests.py +18 -0
- django_cfg/apps/grpc/serializers/services.py +50 -0
- django_cfg/apps/grpc/serializers/stats.py +22 -0
- django_cfg/apps/grpc/services/__init__.py +16 -0
- django_cfg/apps/grpc/services/base.py +375 -0
- django_cfg/apps/grpc/services/discovery.py +415 -0
- django_cfg/apps/grpc/urls.py +23 -0
- django_cfg/apps/grpc/utils/__init__.py +13 -0
- django_cfg/apps/grpc/utils/proto_gen.py +423 -0
- django_cfg/apps/grpc/views/__init__.py +9 -0
- django_cfg/apps/grpc/views/monitoring.py +497 -0
- django_cfg/apps/maintenance/admin/api_key_admin.py +7 -8
- django_cfg/apps/maintenance/admin/site_admin.py +5 -4
- django_cfg/apps/payments/admin/balance_admin.py +26 -36
- django_cfg/apps/payments/admin/payment_admin.py +65 -85
- django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
- django_cfg/apps/tasks/admin/task_log.py +20 -47
- django_cfg/apps/urls.py +7 -1
- django_cfg/config.py +106 -0
- django_cfg/core/base/config_model.py +6 -0
- django_cfg/core/builders/apps_builder.py +3 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
- django_cfg/core/generation/orchestrator.py +10 -0
- django_cfg/models/api/grpc/__init__.py +59 -0
- django_cfg/models/api/grpc/config.py +364 -0
- django_cfg/modules/base.py +15 -0
- django_cfg/modules/django_admin/__init__.py +2 -0
- django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
- django_cfg/modules/django_admin/config/__init__.py +2 -0
- django_cfg/modules/django_admin/config/field_config.py +24 -0
- django_cfg/modules/django_admin/utils/__init__.py +41 -3
- django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
- django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
- django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
- django_cfg/modules/django_admin/utils/html/badges.py +47 -0
- django_cfg/modules/django_admin/utils/html/base.py +167 -0
- django_cfg/modules/django_admin/utils/html/code.py +87 -0
- django_cfg/modules/django_admin/utils/html/composition.py +198 -0
- django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
- django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
- django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
- django_cfg/modules/django_admin/utils/html/progress.py +127 -0
- django_cfg/modules/django_admin/utils/html_builder.py +55 -408
- django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
- django_cfg/modules/django_admin/widgets/registry.py +42 -0
- django_cfg/modules/django_unfold/navigation.py +28 -0
- django_cfg/pyproject.toml +3 -5
- django_cfg/registry/modules.py +6 -0
- {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/METADATA +10 -1
- {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/RECORD +83 -34
- django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
- /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
- /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
- {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base service classes for gRPC.
|
|
3
|
+
|
|
4
|
+
Provides convenient base classes with Django integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
import grpc
|
|
13
|
+
from django.contrib.auth import get_user_model
|
|
14
|
+
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
User = get_user_model()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseService:
|
|
22
|
+
"""
|
|
23
|
+
Base class for gRPC services with Django integration.
|
|
24
|
+
|
|
25
|
+
Features:
|
|
26
|
+
- Easy access to authenticated user
|
|
27
|
+
- Permission checking helpers
|
|
28
|
+
- Error handling utilities
|
|
29
|
+
- Context helpers
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
```python
|
|
33
|
+
from django_cfg.apps.grpc.services.base import BaseService
|
|
34
|
+
from myapp_pb2 import UserResponse
|
|
35
|
+
from myapp_pb2_grpc import UserServiceServicer
|
|
36
|
+
|
|
37
|
+
class UserService(BaseService, UserServiceServicer):
|
|
38
|
+
def GetUser(self, request, context):
|
|
39
|
+
# Get authenticated user
|
|
40
|
+
user = self.get_user(context)
|
|
41
|
+
|
|
42
|
+
# Check permission
|
|
43
|
+
if not user.has_perm('myapp.view_user'):
|
|
44
|
+
self.abort_permission_denied(context, "No permission to view users")
|
|
45
|
+
|
|
46
|
+
# Your logic
|
|
47
|
+
return UserResponse(id=user.id, username=user.username)
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def get_user(self, context: grpc.ServicerContext) -> Optional[User]:
|
|
52
|
+
"""
|
|
53
|
+
Get authenticated user from context.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
context: gRPC servicer context
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Authenticated User instance or None
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> user = self.get_user(context)
|
|
63
|
+
>>> if user:
|
|
64
|
+
... print(f"User: {user.username}")
|
|
65
|
+
"""
|
|
66
|
+
return getattr(context, "user", None)
|
|
67
|
+
|
|
68
|
+
def require_user(self, context: grpc.ServicerContext) -> User:
|
|
69
|
+
"""
|
|
70
|
+
Get authenticated user or abort if not authenticated.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
context: gRPC servicer context
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Authenticated User instance
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
grpc.RpcError: UNAUTHENTICATED if user not authenticated
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> user = self.require_user(context)
|
|
83
|
+
>>> # user is guaranteed to be authenticated here
|
|
84
|
+
"""
|
|
85
|
+
user = self.get_user(context)
|
|
86
|
+
if not user:
|
|
87
|
+
self.abort_unauthenticated(context, "Authentication required")
|
|
88
|
+
return user
|
|
89
|
+
|
|
90
|
+
def check_permission(
|
|
91
|
+
self,
|
|
92
|
+
context: grpc.ServicerContext,
|
|
93
|
+
permission: str,
|
|
94
|
+
obj: Any = None,
|
|
95
|
+
) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
Check if user has permission.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
context: gRPC servicer context
|
|
101
|
+
permission: Permission string (e.g., 'myapp.view_user')
|
|
102
|
+
obj: Object to check permission for (optional)
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if user has permission
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> if not self.check_permission(context, 'myapp.view_user'):
|
|
109
|
+
... self.abort_permission_denied(context)
|
|
110
|
+
"""
|
|
111
|
+
user = self.get_user(context)
|
|
112
|
+
if not user:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
return user.has_perm(permission, obj)
|
|
116
|
+
|
|
117
|
+
def require_permission(
|
|
118
|
+
self,
|
|
119
|
+
context: grpc.ServicerContext,
|
|
120
|
+
permission: str,
|
|
121
|
+
obj: Any = None,
|
|
122
|
+
message: str = None,
|
|
123
|
+
):
|
|
124
|
+
"""
|
|
125
|
+
Require user to have permission or abort.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
context: gRPC servicer context
|
|
129
|
+
permission: Permission string (e.g., 'myapp.view_user')
|
|
130
|
+
obj: Object to check permission for (optional)
|
|
131
|
+
message: Custom error message (optional)
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
grpc.RpcError: PERMISSION_DENIED if permission check fails
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
>>> self.require_permission(context, 'myapp.view_user')
|
|
138
|
+
>>> # user has permission, continue...
|
|
139
|
+
"""
|
|
140
|
+
if not self.check_permission(context, permission, obj):
|
|
141
|
+
if message is None:
|
|
142
|
+
message = f"Permission '{permission}' required"
|
|
143
|
+
self.abort_permission_denied(context, message)
|
|
144
|
+
|
|
145
|
+
def check_staff(self, context: grpc.ServicerContext) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Check if user is staff.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
context: gRPC servicer context
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
True if user is staff
|
|
154
|
+
|
|
155
|
+
Example:
|
|
156
|
+
>>> if self.check_staff(context):
|
|
157
|
+
... # Staff-only logic
|
|
158
|
+
"""
|
|
159
|
+
user = self.get_user(context)
|
|
160
|
+
return user.is_staff if user else False
|
|
161
|
+
|
|
162
|
+
def require_staff(self, context: grpc.ServicerContext, message: str = None):
|
|
163
|
+
"""
|
|
164
|
+
Require user to be staff or abort.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
context: gRPC servicer context
|
|
168
|
+
message: Custom error message (optional)
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
grpc.RpcError: PERMISSION_DENIED if user is not staff
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
>>> self.require_staff(context)
|
|
175
|
+
>>> # user is staff, continue...
|
|
176
|
+
"""
|
|
177
|
+
if not self.check_staff(context):
|
|
178
|
+
if message is None:
|
|
179
|
+
message = "Staff access required"
|
|
180
|
+
self.abort_permission_denied(context, message)
|
|
181
|
+
|
|
182
|
+
def check_superuser(self, context: grpc.ServicerContext) -> bool:
|
|
183
|
+
"""
|
|
184
|
+
Check if user is superuser.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
context: gRPC servicer context
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
True if user is superuser
|
|
191
|
+
|
|
192
|
+
Example:
|
|
193
|
+
>>> if self.check_superuser(context):
|
|
194
|
+
... # Superuser-only logic
|
|
195
|
+
"""
|
|
196
|
+
user = self.get_user(context)
|
|
197
|
+
return user.is_superuser if user else False
|
|
198
|
+
|
|
199
|
+
def require_superuser(self, context: grpc.ServicerContext, message: str = None):
|
|
200
|
+
"""
|
|
201
|
+
Require user to be superuser or abort.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
context: gRPC servicer context
|
|
205
|
+
message: Custom error message (optional)
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
grpc.RpcError: PERMISSION_DENIED if user is not superuser
|
|
209
|
+
|
|
210
|
+
Example:
|
|
211
|
+
>>> self.require_superuser(context)
|
|
212
|
+
>>> # user is superuser, continue...
|
|
213
|
+
"""
|
|
214
|
+
if not self.check_superuser(context):
|
|
215
|
+
if message is None:
|
|
216
|
+
message = "Superuser access required"
|
|
217
|
+
self.abort_permission_denied(context, message)
|
|
218
|
+
|
|
219
|
+
# Error handling helpers
|
|
220
|
+
|
|
221
|
+
def abort(
|
|
222
|
+
self,
|
|
223
|
+
context: grpc.ServicerContext,
|
|
224
|
+
code: grpc.StatusCode,
|
|
225
|
+
message: str,
|
|
226
|
+
):
|
|
227
|
+
"""
|
|
228
|
+
Abort request with error.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
context: gRPC servicer context
|
|
232
|
+
code: gRPC status code
|
|
233
|
+
message: Error message
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
>>> self.abort(context, grpc.StatusCode.INVALID_ARGUMENT, "Invalid user ID")
|
|
237
|
+
"""
|
|
238
|
+
logger.warning(f"Aborting request: {code.name} - {message}")
|
|
239
|
+
context.abort(code, message)
|
|
240
|
+
|
|
241
|
+
def abort_invalid_argument(self, context: grpc.ServicerContext, message: str = "Invalid argument"):
|
|
242
|
+
"""Abort with INVALID_ARGUMENT status."""
|
|
243
|
+
self.abort(context, grpc.StatusCode.INVALID_ARGUMENT, message)
|
|
244
|
+
|
|
245
|
+
def abort_not_found(self, context: grpc.ServicerContext, message: str = "Not found"):
|
|
246
|
+
"""Abort with NOT_FOUND status."""
|
|
247
|
+
self.abort(context, grpc.StatusCode.NOT_FOUND, message)
|
|
248
|
+
|
|
249
|
+
def abort_permission_denied(self, context: grpc.ServicerContext, message: str = "Permission denied"):
|
|
250
|
+
"""Abort with PERMISSION_DENIED status."""
|
|
251
|
+
self.abort(context, grpc.StatusCode.PERMISSION_DENIED, message)
|
|
252
|
+
|
|
253
|
+
def abort_unauthenticated(self, context: grpc.ServicerContext, message: str = "Unauthenticated"):
|
|
254
|
+
"""Abort with UNAUTHENTICATED status."""
|
|
255
|
+
self.abort(context, grpc.StatusCode.UNAUTHENTICATED, message)
|
|
256
|
+
|
|
257
|
+
def abort_unimplemented(self, context: grpc.ServicerContext, message: str = "Not implemented"):
|
|
258
|
+
"""Abort with UNIMPLEMENTED status."""
|
|
259
|
+
self.abort(context, grpc.StatusCode.UNIMPLEMENTED, message)
|
|
260
|
+
|
|
261
|
+
def abort_internal(self, context: grpc.ServicerContext, message: str = "Internal server error"):
|
|
262
|
+
"""Abort with INTERNAL status."""
|
|
263
|
+
self.abort(context, grpc.StatusCode.INTERNAL, message)
|
|
264
|
+
|
|
265
|
+
# Context helpers
|
|
266
|
+
|
|
267
|
+
def get_metadata(self, context: grpc.ServicerContext, key: str) -> Optional[str]:
|
|
268
|
+
"""
|
|
269
|
+
Get metadata value from context.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
context: gRPC servicer context
|
|
273
|
+
key: Metadata key
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Metadata value or None
|
|
277
|
+
|
|
278
|
+
Example:
|
|
279
|
+
>>> user_agent = self.get_metadata(context, 'user-agent')
|
|
280
|
+
"""
|
|
281
|
+
metadata = dict(context.invocation_metadata())
|
|
282
|
+
return metadata.get(key.lower())
|
|
283
|
+
|
|
284
|
+
def set_metadata(self, context: grpc.ServicerContext, key: str, value: str):
|
|
285
|
+
"""
|
|
286
|
+
Set response metadata.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
context: gRPC servicer context
|
|
290
|
+
key: Metadata key
|
|
291
|
+
value: Metadata value
|
|
292
|
+
|
|
293
|
+
Example:
|
|
294
|
+
>>> self.set_metadata(context, 'x-request-id', request_id)
|
|
295
|
+
"""
|
|
296
|
+
context.set_trailing_metadata([(key, value)])
|
|
297
|
+
|
|
298
|
+
def get_peer(self, context: grpc.ServicerContext) -> str:
|
|
299
|
+
"""
|
|
300
|
+
Get peer information.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
context: gRPC servicer context
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Peer string
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
>>> peer = self.get_peer(context)
|
|
310
|
+
>>> print(f"Request from: {peer}")
|
|
311
|
+
"""
|
|
312
|
+
return context.peer()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class ReadOnlyService(BaseService):
|
|
316
|
+
"""
|
|
317
|
+
Base class for read-only gRPC services.
|
|
318
|
+
|
|
319
|
+
Automatically aborts on write operations.
|
|
320
|
+
|
|
321
|
+
Example:
|
|
322
|
+
```python
|
|
323
|
+
class UserReadService(ReadOnlyService, UserServiceServicer):
|
|
324
|
+
def GetUser(self, request, context):
|
|
325
|
+
# Allowed
|
|
326
|
+
return UserResponse(...)
|
|
327
|
+
|
|
328
|
+
def CreateUser(self, request, context):
|
|
329
|
+
# Will abort with PERMISSION_DENIED
|
|
330
|
+
self.abort_readonly(context)
|
|
331
|
+
```
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
def abort_readonly(self, context: grpc.ServicerContext, message: str = "Read-only service"):
|
|
335
|
+
"""Abort with PERMISSION_DENIED for write operations."""
|
|
336
|
+
self.abort_permission_denied(context, message)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class AuthRequiredService(BaseService):
|
|
340
|
+
"""
|
|
341
|
+
Base class for services that require authentication.
|
|
342
|
+
|
|
343
|
+
Automatically checks for authenticated user in all methods.
|
|
344
|
+
|
|
345
|
+
Example:
|
|
346
|
+
```python
|
|
347
|
+
class UserService(AuthRequiredService, UserServiceServicer):
|
|
348
|
+
def GetUser(self, request, context):
|
|
349
|
+
# User is automatically required
|
|
350
|
+
user = self.user # Guaranteed to be authenticated
|
|
351
|
+
return UserResponse(...)
|
|
352
|
+
```
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def __init__(self):
|
|
356
|
+
super().__init__()
|
|
357
|
+
self._user = None
|
|
358
|
+
|
|
359
|
+
def _ensure_auth(self, context: grpc.ServicerContext):
|
|
360
|
+
"""Ensure user is authenticated."""
|
|
361
|
+
self._user = self.require_user(context)
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def user(self) -> User:
|
|
365
|
+
"""Get authenticated user (must call _ensure_auth first)."""
|
|
366
|
+
if self._user is None:
|
|
367
|
+
raise RuntimeError("User not set - did you call _ensure_auth?")
|
|
368
|
+
return self._user
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
__all__ = [
|
|
372
|
+
"BaseService",
|
|
373
|
+
"ReadOnlyService",
|
|
374
|
+
"AuthRequiredService",
|
|
375
|
+
]
|