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.
- django_cfg/__init__.py +8 -4
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
- django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/grpc/__init__.py +9 -0
- django_cfg/apps/grpc/admin/__init__.py +11 -0
- django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
- 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/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/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/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
- django_cfg/apps/maintenance/admin/site_admin.py +5 -4
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- 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/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +13 -8
- django_cfg/config.py +106 -0
- django_cfg/core/base/config_model.py +16 -26
- django_cfg/core/builders/apps_builder.py +7 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
- django_cfg/core/generation/orchestrator.py +15 -15
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/api/grpc/__init__.py +59 -0
- django_cfg/models/api/grpc/config.py +364 -0
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +19 -6
- django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- 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 +205 -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_unfold/navigation.py +21 -18
- django_cfg/pyproject.toml +4 -6
- django_cfg/registry/core.py +4 -7
- django_cfg/registry/modules.py +6 -0
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/task_log.py +0 -265
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
- /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
- /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.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.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
|
+
]
|