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.

Files changed (84) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/grpc/__init__.py +9 -0
  4. django_cfg/apps/grpc/admin/__init__.py +11 -0
  5. django_cfg/apps/grpc/admin/config.py +89 -0
  6. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  7. django_cfg/apps/grpc/apps.py +28 -0
  8. django_cfg/apps/grpc/auth/__init__.py +9 -0
  9. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  10. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  11. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  12. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  13. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  14. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  15. django_cfg/apps/grpc/management/__init__.py +1 -0
  16. django_cfg/apps/grpc/management/commands/__init__.py +0 -0
  17. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  18. django_cfg/apps/grpc/managers/__init__.py +10 -0
  19. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  20. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  21. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  22. django_cfg/apps/grpc/migrations/__init__.py +0 -0
  23. django_cfg/apps/grpc/models/__init__.py +9 -0
  24. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  25. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  26. django_cfg/apps/grpc/serializers/health.py +18 -0
  27. django_cfg/apps/grpc/serializers/requests.py +18 -0
  28. django_cfg/apps/grpc/serializers/services.py +50 -0
  29. django_cfg/apps/grpc/serializers/stats.py +22 -0
  30. django_cfg/apps/grpc/services/__init__.py +16 -0
  31. django_cfg/apps/grpc/services/base.py +375 -0
  32. django_cfg/apps/grpc/services/discovery.py +415 -0
  33. django_cfg/apps/grpc/urls.py +23 -0
  34. django_cfg/apps/grpc/utils/__init__.py +13 -0
  35. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  36. django_cfg/apps/grpc/views/__init__.py +9 -0
  37. django_cfg/apps/grpc/views/monitoring.py +497 -0
  38. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -8
  39. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  40. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  41. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  42. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  43. django_cfg/apps/tasks/admin/task_log.py +20 -47
  44. django_cfg/apps/urls.py +7 -1
  45. django_cfg/config.py +106 -0
  46. django_cfg/core/base/config_model.py +6 -0
  47. django_cfg/core/builders/apps_builder.py +3 -0
  48. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  49. django_cfg/core/generation/orchestrator.py +10 -0
  50. django_cfg/models/api/grpc/__init__.py +59 -0
  51. django_cfg/models/api/grpc/config.py +364 -0
  52. django_cfg/modules/base.py +15 -0
  53. django_cfg/modules/django_admin/__init__.py +2 -0
  54. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  55. django_cfg/modules/django_admin/config/__init__.py +2 -0
  56. django_cfg/modules/django_admin/config/field_config.py +24 -0
  57. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  58. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  59. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  60. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  61. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  62. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  63. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  64. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  65. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  66. django_cfg/modules/django_admin/utils/html/composition.py +198 -0
  67. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  68. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  69. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  70. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  71. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  72. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  73. django_cfg/modules/django_admin/widgets/registry.py +42 -0
  74. django_cfg/modules/django_unfold/navigation.py +28 -0
  75. django_cfg/pyproject.toml +3 -5
  76. django_cfg/registry/modules.py +6 -0
  77. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/METADATA +10 -1
  78. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/RECORD +83 -34
  79. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  80. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  81. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  82. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/WHEEL +0 -0
  83. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/entry_points.txt +0 -0
  84. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,423 @@
1
+ """
2
+ Proto file generation utilities.
3
+
4
+ Helps generate .proto files from Django models.
5
+ """
6
+
7
+ import logging
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from django.apps import apps
12
+ from django.conf import settings
13
+ from django.db import models
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class ProtoFieldMapper:
19
+ """
20
+ Maps Django model fields to Protobuf types.
21
+
22
+ Example:
23
+ ```python
24
+ mapper = ProtoFieldMapper()
25
+ proto_type = mapper.get_proto_type(model_field)
26
+ # 'string', 'int32', 'bool', etc.
27
+ ```
28
+ """
29
+
30
+ # Django field -> Proto type mapping
31
+ FIELD_TYPE_MAP = {
32
+ models.CharField: "string",
33
+ models.TextField: "string",
34
+ models.EmailField: "string",
35
+ models.URLField: "string",
36
+ models.SlugField: "string",
37
+ models.UUIDField: "string",
38
+ models.IntegerField: "int32",
39
+ models.BigIntegerField: "int64",
40
+ models.SmallIntegerField: "int32",
41
+ models.PositiveIntegerField: "uint32",
42
+ models.PositiveBigIntegerField: "uint64",
43
+ models.PositiveSmallIntegerField: "uint32",
44
+ models.FloatField: "float",
45
+ models.DecimalField: "string", # Decimal as string to avoid precision loss
46
+ models.BooleanField: "bool",
47
+ models.DateField: "string", # ISO 8601 date string
48
+ models.DateTimeField: "string", # ISO 8601 datetime string
49
+ models.TimeField: "string", # ISO 8601 time string
50
+ models.DurationField: "string", # Duration as string
51
+ models.JSONField: "string", # JSON as string
52
+ models.BinaryField: "bytes",
53
+ models.FileField: "string", # File path/URL
54
+ models.ImageField: "string", # Image path/URL
55
+ }
56
+
57
+ def get_proto_type(self, field: models.Field) -> str:
58
+ """
59
+ Get Protobuf type for Django field.
60
+
61
+ Args:
62
+ field: Django model field
63
+
64
+ Returns:
65
+ Protobuf type string
66
+
67
+ Example:
68
+ >>> field = models.CharField(max_length=100)
69
+ >>> mapper.get_proto_type(field)
70
+ 'string'
71
+ """
72
+ field_class = type(field)
73
+
74
+ # Check for foreign key
75
+ if isinstance(field, models.ForeignKey):
76
+ return "int64" # ID of related object
77
+
78
+ # Check for many-to-many
79
+ if isinstance(field, models.ManyToManyField):
80
+ return "repeated int64" # IDs of related objects
81
+
82
+ # Check for one-to-one
83
+ if isinstance(field, models.OneToOneField):
84
+ return "int64" # ID of related object
85
+
86
+ # Check field type map
87
+ for django_field_type, proto_type in self.FIELD_TYPE_MAP.items():
88
+ if isinstance(field, django_field_type):
89
+ return proto_type
90
+
91
+ # Default to string
92
+ logger.warning(f"Unknown field type {field_class.__name__}, defaulting to string")
93
+ return "string"
94
+
95
+ def is_repeated(self, field: models.Field) -> bool:
96
+ """
97
+ Check if field should be repeated in proto.
98
+
99
+ Args:
100
+ field: Django model field
101
+
102
+ Returns:
103
+ True if field should be repeated
104
+ """
105
+ return isinstance(field, models.ManyToManyField)
106
+
107
+ def is_optional(self, field: models.Field) -> bool:
108
+ """
109
+ Check if field should be optional in proto.
110
+
111
+ Args:
112
+ field: Django model field
113
+
114
+ Returns:
115
+ True if field should be optional
116
+ """
117
+ return field.null or field.blank or hasattr(field, "default")
118
+
119
+
120
+ class ProtoGenerator:
121
+ """
122
+ Generates .proto files from Django models.
123
+
124
+ Features:
125
+ - Auto-generates message definitions
126
+ - Handles field types
127
+ - Supports relationships
128
+ - Configurable naming conventions
129
+
130
+ Example:
131
+ ```python
132
+ from myapp.models import User
133
+
134
+ generator = ProtoGenerator()
135
+ proto_content = generator.generate_message(User)
136
+
137
+ # Write to file
138
+ with open('user.proto', 'w') as f:
139
+ f.write(proto_content)
140
+ ```
141
+ """
142
+
143
+ def __init__(self, package_prefix: str = "", field_naming: str = "snake_case"):
144
+ """
145
+ Initialize proto generator.
146
+
147
+ Args:
148
+ package_prefix: Package prefix for proto files
149
+ field_naming: Field naming convention ('snake_case' or 'camelCase')
150
+ """
151
+ self.package_prefix = package_prefix
152
+ self.field_naming = field_naming
153
+ self.mapper = ProtoFieldMapper()
154
+
155
+ def generate_message(
156
+ self,
157
+ model: type,
158
+ include_id: bool = True,
159
+ include_timestamps: bool = True,
160
+ ) -> str:
161
+ """
162
+ Generate protobuf message for Django model.
163
+
164
+ Args:
165
+ model: Django model class
166
+ include_id: Include id field
167
+ include_timestamps: Include created_at/updated_at fields
168
+
169
+ Returns:
170
+ Proto message definition string
171
+
172
+ Example:
173
+ >>> from myapp.models import User
174
+ >>> generator = ProtoGenerator()
175
+ >>> print(generator.generate_message(User))
176
+ message User {
177
+ int64 id = 1;
178
+ string username = 2;
179
+ string email = 3;
180
+ }
181
+ """
182
+ message_name = model.__name__
183
+ lines = [f"message {message_name} {{"]
184
+
185
+ field_number = 1
186
+
187
+ # Add id field
188
+ if include_id:
189
+ lines.append(f" int64 id = {field_number};")
190
+ field_number += 1
191
+
192
+ # Add model fields
193
+ for field in model._meta.get_fields():
194
+ # Skip reverse relations
195
+ if field.auto_created and not field.concrete:
196
+ continue
197
+
198
+ # Skip many-to-many for now (handle separately)
199
+ if isinstance(field, models.ManyToManyField):
200
+ continue
201
+
202
+ # Get field info
203
+ field_name = self._format_field_name(field.name)
204
+ proto_type = self.mapper.get_proto_type(field)
205
+ is_optional = self.mapper.is_optional(field)
206
+
207
+ # Build field definition
208
+ if is_optional and not field.primary_key:
209
+ field_def = f" optional {proto_type} {field_name} = {field_number};"
210
+ else:
211
+ field_def = f" {proto_type} {field_name} = {field_number};"
212
+
213
+ lines.append(field_def)
214
+ field_number += 1
215
+
216
+ # Add timestamp fields if requested
217
+ if include_timestamps:
218
+ if hasattr(model, "created_at"):
219
+ lines.append(f" string created_at = {field_number};")
220
+ field_number += 1
221
+ if hasattr(model, "updated_at"):
222
+ lines.append(f" string updated_at = {field_number};")
223
+ field_number += 1
224
+
225
+ lines.append("}")
226
+
227
+ return "\n".join(lines)
228
+
229
+ def generate_service(
230
+ self,
231
+ service_name: str,
232
+ model: type,
233
+ methods: Optional[List[str]] = None,
234
+ ) -> str:
235
+ """
236
+ Generate protobuf service definition.
237
+
238
+ Args:
239
+ service_name: Service name
240
+ model: Django model class
241
+ methods: List of methods to include (default: CRUD)
242
+
243
+ Returns:
244
+ Proto service definition string
245
+
246
+ Example:
247
+ >>> from myapp.models import User
248
+ >>> generator = ProtoGenerator()
249
+ >>> print(generator.generate_service("UserService", User))
250
+ service UserService {
251
+ rpc Create(CreateUserRequest) returns (User);
252
+ rpc Get(GetUserRequest) returns (User);
253
+ rpc Update(UpdateUserRequest) returns (User);
254
+ rpc Delete(DeleteUserRequest) returns (Empty);
255
+ rpc List(ListUserRequest) returns (ListUserResponse);
256
+ }
257
+ """
258
+ if methods is None:
259
+ methods = ["Create", "Get", "Update", "Delete", "List"]
260
+
261
+ model_name = model.__name__
262
+ lines = [f"service {service_name} {{"]
263
+
264
+ for method in methods:
265
+ if method == "Create":
266
+ lines.append(f" rpc Create(Create{model_name}Request) returns ({model_name});")
267
+ elif method == "Get":
268
+ lines.append(f" rpc Get(Get{model_name}Request) returns ({model_name});")
269
+ elif method == "Update":
270
+ lines.append(f" rpc Update(Update{model_name}Request) returns ({model_name});")
271
+ elif method == "Delete":
272
+ lines.append(f" rpc Delete(Delete{model_name}Request) returns (google.protobuf.Empty);")
273
+ elif method == "List":
274
+ lines.append(f" rpc List(List{model_name}Request) returns (List{model_name}Response);")
275
+
276
+ lines.append("}")
277
+
278
+ return "\n".join(lines)
279
+
280
+ def generate_proto_file(
281
+ self,
282
+ models_list: List[type],
283
+ service_name: Optional[str] = None,
284
+ output_path: Optional[Path] = None,
285
+ ) -> str:
286
+ """
287
+ Generate complete .proto file for models.
288
+
289
+ Args:
290
+ models_list: List of Django model classes
291
+ service_name: Optional service name
292
+ output_path: Optional output file path
293
+
294
+ Returns:
295
+ Complete proto file content
296
+
297
+ Example:
298
+ >>> from myapp.models import User, Post
299
+ >>> generator = ProtoGenerator()
300
+ >>> content = generator.generate_proto_file(
301
+ ... [User, Post],
302
+ ... service_name="MyAppService"
303
+ ... )
304
+ """
305
+ lines = [
306
+ 'syntax = "proto3";',
307
+ "",
308
+ ]
309
+
310
+ # Add package
311
+ if self.package_prefix:
312
+ lines.append(f"package {self.package_prefix};")
313
+ lines.append("")
314
+
315
+ # Add imports
316
+ lines.append('import "google/protobuf/empty.proto";')
317
+ lines.append("")
318
+
319
+ # Add messages
320
+ for model in models_list:
321
+ message = self.generate_message(model)
322
+ lines.append(message)
323
+ lines.append("")
324
+
325
+ # Add service if requested
326
+ if service_name and models_list:
327
+ service = self.generate_service(service_name, models_list[0])
328
+ lines.append(service)
329
+ lines.append("")
330
+
331
+ content = "\n".join(lines)
332
+
333
+ # Write to file if path provided
334
+ if output_path:
335
+ output_path.parent.mkdir(parents=True, exist_ok=True)
336
+ output_path.write_text(content)
337
+ logger.info(f"Generated proto file: {output_path}")
338
+
339
+ return content
340
+
341
+ def _format_field_name(self, name: str) -> str:
342
+ """
343
+ Format field name according to naming convention.
344
+
345
+ Args:
346
+ name: Original field name
347
+
348
+ Returns:
349
+ Formatted field name
350
+ """
351
+ if self.field_naming == "camelCase":
352
+ parts = name.split("_")
353
+ return parts[0] + "".join(p.capitalize() for p in parts[1:])
354
+ else:
355
+ # snake_case (default)
356
+ return name
357
+
358
+
359
+ def generate_proto_for_app(app_label: str, output_dir: Optional[Path] = None) -> int:
360
+ """
361
+ Generate proto files for all models in an app.
362
+
363
+ Args:
364
+ app_label: Django app label
365
+ output_dir: Output directory (default: protos/)
366
+
367
+ Returns:
368
+ Number of proto files generated
369
+
370
+ Example:
371
+ ```python
372
+ from django_cfg.apps.grpc.utils.proto_gen import generate_proto_for_app
373
+
374
+ count = generate_proto_for_app('myapp')
375
+ print(f"Generated {count} proto file(s)")
376
+ ```
377
+ """
378
+ # Get output directory
379
+ if output_dir is None:
380
+ grpc_proto_config = getattr(settings, "GRPC_PROTO", {})
381
+ output_dir_str = grpc_proto_config.get("output_dir", "protos")
382
+ output_dir = Path(output_dir_str)
383
+
384
+ # Get app config
385
+ try:
386
+ app_config = apps.get_app_config(app_label)
387
+ except LookupError:
388
+ logger.error(f"App '{app_label}' not found")
389
+ return 0
390
+
391
+ # Get models
392
+ models_list = [
393
+ model for model in app_config.get_models()
394
+ if not model._meta.abstract
395
+ ]
396
+
397
+ if not models_list:
398
+ logger.warning(f"No models found in app '{app_label}'")
399
+ return 0
400
+
401
+ # Generate proto file
402
+ generator = ProtoGenerator(
403
+ package_prefix=app_label,
404
+ field_naming="snake_case",
405
+ )
406
+
407
+ output_path = output_dir / f"{app_label}.proto"
408
+
409
+ generator.generate_proto_file(
410
+ models_list,
411
+ service_name=f"{app_label.capitalize()}Service",
412
+ output_path=output_path,
413
+ )
414
+
415
+ logger.info(f"Generated proto file for app '{app_label}' with {len(models_list)} model(s)")
416
+ return 1
417
+
418
+
419
+ __all__ = [
420
+ "ProtoFieldMapper",
421
+ "ProtoGenerator",
422
+ "generate_proto_for_app",
423
+ ]
@@ -0,0 +1,9 @@
1
+ """
2
+ Views for gRPC monitoring API.
3
+ """
4
+
5
+ from .monitoring import GRPCMonitorViewSet
6
+
7
+ __all__ = [
8
+ "GRPCMonitorViewSet",
9
+ ]