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,364 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC Configuration Models
|
|
3
|
+
|
|
4
|
+
Type-safe Pydantic v2 models for gRPC server, authentication, and proto generation.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from django_cfg.models.api.grpc import GRPCConfig
|
|
8
|
+
>>> config = GRPCConfig(
|
|
9
|
+
... enabled=True,
|
|
10
|
+
... server=GRPCServerConfig(port=50051),
|
|
11
|
+
... auth=GRPCAuthConfig(require_auth=True)
|
|
12
|
+
... )
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import warnings
|
|
16
|
+
from typing import Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from pydantic import Field, field_validator, model_validator
|
|
19
|
+
|
|
20
|
+
from django_cfg.models.base import BaseConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GRPCServerConfig(BaseConfig):
|
|
24
|
+
"""
|
|
25
|
+
gRPC server configuration.
|
|
26
|
+
|
|
27
|
+
Configures the gRPC server including host, port, workers, compression,
|
|
28
|
+
message limits, and keepalive settings.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> config = GRPCServerConfig(
|
|
32
|
+
... host="0.0.0.0",
|
|
33
|
+
... port=50051,
|
|
34
|
+
... max_workers=10,
|
|
35
|
+
... compression="gzip"
|
|
36
|
+
... )
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
enabled: bool = Field(
|
|
40
|
+
default=True,
|
|
41
|
+
description="Enable gRPC server",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
host: str = Field(
|
|
45
|
+
default="[::]",
|
|
46
|
+
description="Server bind address (IPv6 by default, use 0.0.0.0 for IPv4)",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
port: int = Field(
|
|
50
|
+
default=50051,
|
|
51
|
+
description="Server port",
|
|
52
|
+
ge=1024,
|
|
53
|
+
le=65535,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
max_workers: int = Field(
|
|
57
|
+
default=10,
|
|
58
|
+
description="ThreadPoolExecutor max workers",
|
|
59
|
+
ge=1,
|
|
60
|
+
le=1000,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
enable_reflection: bool = Field(
|
|
64
|
+
default=False,
|
|
65
|
+
description="Enable server reflection for dynamic clients (grpcurl, etc.)",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
enable_health_check: bool = Field(
|
|
69
|
+
default=True,
|
|
70
|
+
description="Enable gRPC health check service",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
compression: Optional[str] = Field(
|
|
74
|
+
default=None,
|
|
75
|
+
description="Compression algorithm: 'gzip', 'deflate', or None",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
max_send_message_length: int = Field(
|
|
79
|
+
default=4 * 1024 * 1024, # 4 MB
|
|
80
|
+
description="Maximum outbound message size in bytes",
|
|
81
|
+
ge=1024, # Min 1KB
|
|
82
|
+
le=100 * 1024 * 1024, # Max 100MB
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
max_receive_message_length: int = Field(
|
|
86
|
+
default=4 * 1024 * 1024, # 4 MB
|
|
87
|
+
description="Maximum inbound message size in bytes",
|
|
88
|
+
ge=1024,
|
|
89
|
+
le=100 * 1024 * 1024,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
keepalive_time_ms: int = Field(
|
|
93
|
+
default=7200000, # 2 hours
|
|
94
|
+
description="Keepalive ping interval in milliseconds",
|
|
95
|
+
ge=1000, # Min 1 second
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
keepalive_timeout_ms: int = Field(
|
|
99
|
+
default=20000, # 20 seconds
|
|
100
|
+
description="Keepalive ping timeout in milliseconds",
|
|
101
|
+
ge=1000,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
interceptors: List[str] = Field(
|
|
105
|
+
default_factory=list,
|
|
106
|
+
description="Server interceptor import paths (e.g., 'myapp.interceptors.AuthInterceptor')",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@field_validator("compression")
|
|
110
|
+
@classmethod
|
|
111
|
+
def validate_compression(cls, v: Optional[str]) -> Optional[str]:
|
|
112
|
+
"""Validate compression algorithm."""
|
|
113
|
+
if v and v not in ("gzip", "deflate"):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Invalid compression: {v}. Must be 'gzip', 'deflate', or None"
|
|
116
|
+
)
|
|
117
|
+
return v
|
|
118
|
+
|
|
119
|
+
@field_validator("host")
|
|
120
|
+
@classmethod
|
|
121
|
+
def validate_host(cls, v: str) -> str:
|
|
122
|
+
"""Validate host format."""
|
|
123
|
+
if not v or not v.strip():
|
|
124
|
+
raise ValueError("Host cannot be empty")
|
|
125
|
+
return v.strip()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class GRPCAuthConfig(BaseConfig):
|
|
129
|
+
"""
|
|
130
|
+
gRPC authentication configuration.
|
|
131
|
+
|
|
132
|
+
Supports JWT authentication with configurable token handling.
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
>>> config = GRPCAuthConfig(
|
|
136
|
+
... enabled=True,
|
|
137
|
+
... require_auth=True,
|
|
138
|
+
... jwt_algorithm="HS256"
|
|
139
|
+
... )
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
enabled: bool = Field(
|
|
143
|
+
default=True,
|
|
144
|
+
description="Enable authentication",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
require_auth: bool = Field(
|
|
148
|
+
default=True,
|
|
149
|
+
description="Require authentication for all services (except public_methods)",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
token_header: str = Field(
|
|
153
|
+
default="authorization",
|
|
154
|
+
description="Metadata key for auth token",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
token_prefix: str = Field(
|
|
158
|
+
default="Bearer",
|
|
159
|
+
description="Token prefix (e.g., 'Bearer' for JWT)",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
jwt_secret_key: Optional[str] = Field(
|
|
163
|
+
default=None,
|
|
164
|
+
description="JWT secret key (defaults to Django SECRET_KEY if None)",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
jwt_algorithm: str = Field(
|
|
168
|
+
default="HS256",
|
|
169
|
+
description="JWT signing algorithm",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
jwt_verify_exp: bool = Field(
|
|
173
|
+
default=True,
|
|
174
|
+
description="Verify JWT expiration",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
jwt_leeway: int = Field(
|
|
178
|
+
default=0,
|
|
179
|
+
description="JWT expiration leeway in seconds",
|
|
180
|
+
ge=0,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
public_methods: List[str] = Field(
|
|
184
|
+
default_factory=lambda: [
|
|
185
|
+
"/grpc.health.v1.Health/Check",
|
|
186
|
+
"/grpc.health.v1.Health/Watch",
|
|
187
|
+
],
|
|
188
|
+
description="RPC methods that don't require authentication",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@field_validator("jwt_algorithm")
|
|
192
|
+
@classmethod
|
|
193
|
+
def validate_jwt_algorithm(cls, v: str) -> str:
|
|
194
|
+
"""Validate JWT algorithm."""
|
|
195
|
+
valid_algorithms = {
|
|
196
|
+
"HS256",
|
|
197
|
+
"HS384",
|
|
198
|
+
"HS512",
|
|
199
|
+
"RS256",
|
|
200
|
+
"RS384",
|
|
201
|
+
"RS512",
|
|
202
|
+
"ES256",
|
|
203
|
+
"ES384",
|
|
204
|
+
"ES512",
|
|
205
|
+
}
|
|
206
|
+
if v not in valid_algorithms:
|
|
207
|
+
raise ValueError(
|
|
208
|
+
f"Invalid JWT algorithm: {v}. "
|
|
209
|
+
f"Must be one of: {', '.join(sorted(valid_algorithms))}"
|
|
210
|
+
)
|
|
211
|
+
return v
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class GRPCProtoConfig(BaseConfig):
|
|
215
|
+
"""
|
|
216
|
+
Proto file generation configuration.
|
|
217
|
+
|
|
218
|
+
Controls automatic proto file generation from Django models.
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
>>> config = GRPCProtoConfig(
|
|
222
|
+
... auto_generate=True,
|
|
223
|
+
... output_dir="protos",
|
|
224
|
+
... package_prefix="mycompany"
|
|
225
|
+
... )
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
auto_generate: bool = Field(
|
|
229
|
+
default=True,
|
|
230
|
+
description="Auto-generate proto files from Django models",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
output_dir: str = Field(
|
|
234
|
+
default="protos",
|
|
235
|
+
description="Proto files output directory (relative to BASE_DIR)",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
package_prefix: str = Field(
|
|
239
|
+
default="",
|
|
240
|
+
description="Package prefix for all generated protos (e.g., 'mycompany')",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
include_services: bool = Field(
|
|
244
|
+
default=True,
|
|
245
|
+
description="Include service definitions in generated protos",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
field_naming: str = Field(
|
|
249
|
+
default="snake_case",
|
|
250
|
+
description="Proto field naming convention",
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
@field_validator("field_naming")
|
|
254
|
+
@classmethod
|
|
255
|
+
def validate_field_naming(cls, v: str) -> str:
|
|
256
|
+
"""Validate field naming convention."""
|
|
257
|
+
if v not in ("snake_case", "camelCase"):
|
|
258
|
+
raise ValueError(
|
|
259
|
+
f"Invalid field_naming: {v}. Must be 'snake_case' or 'camelCase'"
|
|
260
|
+
)
|
|
261
|
+
return v
|
|
262
|
+
|
|
263
|
+
@field_validator("output_dir")
|
|
264
|
+
@classmethod
|
|
265
|
+
def validate_output_dir(cls, v: str) -> str:
|
|
266
|
+
"""Validate output directory."""
|
|
267
|
+
if not v or not v.strip():
|
|
268
|
+
raise ValueError("output_dir cannot be empty")
|
|
269
|
+
# Remove leading/trailing slashes
|
|
270
|
+
return v.strip().strip("/")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class GRPCConfig(BaseConfig):
|
|
274
|
+
"""
|
|
275
|
+
Main gRPC configuration.
|
|
276
|
+
|
|
277
|
+
Combines server, authentication, and proto generation settings.
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
Basic setup:
|
|
281
|
+
>>> config = GRPCConfig(enabled=True)
|
|
282
|
+
|
|
283
|
+
Advanced setup:
|
|
284
|
+
>>> config = GRPCConfig(
|
|
285
|
+
... enabled=True,
|
|
286
|
+
... server=GRPCServerConfig(port=8080, max_workers=50),
|
|
287
|
+
... auth=GRPCAuthConfig(require_auth=True),
|
|
288
|
+
... auto_register_apps=["accounts", "support"]
|
|
289
|
+
... )
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
enabled: bool = Field(
|
|
293
|
+
default=False,
|
|
294
|
+
description="Enable gRPC integration",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
server: GRPCServerConfig = Field(
|
|
298
|
+
default_factory=GRPCServerConfig,
|
|
299
|
+
description="Server configuration",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
auth: GRPCAuthConfig = Field(
|
|
303
|
+
default_factory=GRPCAuthConfig,
|
|
304
|
+
description="Authentication configuration",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
proto: GRPCProtoConfig = Field(
|
|
308
|
+
default_factory=GRPCProtoConfig,
|
|
309
|
+
description="Proto generation configuration",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
handlers_hook: str = Field(
|
|
313
|
+
default="{ROOT_URLCONF}.grpc_handlers",
|
|
314
|
+
description="Import path to grpc_handlers function",
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
auto_register_apps: bool = Field(
|
|
318
|
+
default=True,
|
|
319
|
+
description="Auto-register gRPC services for Django-CFG apps",
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
enabled_apps: List[str] = Field(
|
|
323
|
+
default_factory=lambda: [
|
|
324
|
+
"accounts",
|
|
325
|
+
"support",
|
|
326
|
+
"knowbase",
|
|
327
|
+
"agents",
|
|
328
|
+
"payments",
|
|
329
|
+
"leads",
|
|
330
|
+
],
|
|
331
|
+
description="Django-CFG apps to expose via gRPC (if auto_register_apps=True)",
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
custom_services: Dict[str, str] = Field(
|
|
335
|
+
default_factory=dict,
|
|
336
|
+
description="Custom service import paths: {service_name: 'path.to.Service'}",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
@model_validator(mode="after")
|
|
340
|
+
def validate_grpc_config(self) -> "GRPCConfig":
|
|
341
|
+
"""Cross-field validation."""
|
|
342
|
+
# Check dependencies if enabled
|
|
343
|
+
if self.enabled:
|
|
344
|
+
from django_cfg.config import require_feature
|
|
345
|
+
|
|
346
|
+
require_feature("grpc")
|
|
347
|
+
|
|
348
|
+
# Validate server enabled
|
|
349
|
+
if not self.server.enabled:
|
|
350
|
+
raise ValueError(
|
|
351
|
+
"Cannot enable gRPC with server disabled. "
|
|
352
|
+
"Set server.enabled=True or grpc.enabled=False"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Warn if auto_register but no apps
|
|
356
|
+
if self.auto_register_apps and not self.enabled_apps:
|
|
357
|
+
warnings.warn(
|
|
358
|
+
"auto_register_apps is True but enabled_apps is empty. "
|
|
359
|
+
"No services will be auto-registered.",
|
|
360
|
+
UserWarning,
|
|
361
|
+
stacklevel=2,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return self
|
django_cfg/modules/base.py
CHANGED
|
@@ -206,6 +206,21 @@ class BaseCfgModule(ABC):
|
|
|
206
206
|
|
|
207
207
|
return False
|
|
208
208
|
|
|
209
|
+
def is_grpc_enabled(self) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Check if django-cfg gRPC is enabled.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
True if gRPC is enabled, False otherwise
|
|
215
|
+
"""
|
|
216
|
+
grpc_config = self._get_config_key('grpc', None)
|
|
217
|
+
|
|
218
|
+
# Check if grpc config exists and is enabled
|
|
219
|
+
if grpc_config and hasattr(grpc_config, 'enabled'):
|
|
220
|
+
return grpc_config.enabled
|
|
221
|
+
|
|
222
|
+
return False
|
|
223
|
+
|
|
209
224
|
|
|
210
225
|
# Export the base class
|
|
211
226
|
__all__ = [
|
|
@@ -57,6 +57,7 @@ from .config import (
|
|
|
57
57
|
ImageField,
|
|
58
58
|
MarkdownField,
|
|
59
59
|
ResourceConfig,
|
|
60
|
+
ShortUUIDField,
|
|
60
61
|
TextField,
|
|
61
62
|
UserField,
|
|
62
63
|
)
|
|
@@ -116,6 +117,7 @@ __all__ = [
|
|
|
116
117
|
"DateTimeField",
|
|
117
118
|
"ImageField",
|
|
118
119
|
"MarkdownField",
|
|
120
|
+
"ShortUUIDField",
|
|
119
121
|
"TextField",
|
|
120
122
|
"UserField",
|
|
121
123
|
# Advanced
|
|
@@ -523,7 +523,7 @@ class PydanticAdminMixin:
|
|
|
523
523
|
|
|
524
524
|
# Add Mermaid resources if plugins enabled
|
|
525
525
|
if doc_config.enable_plugins:
|
|
526
|
-
from django_cfg.modules.django_admin.utils.mermaid_plugin import get_mermaid_resources
|
|
526
|
+
from django_cfg.modules.django_admin.utils.markdown.mermaid_plugin import get_mermaid_resources
|
|
527
527
|
extra_context['mermaid_resources'] = get_mermaid_resources()
|
|
528
528
|
|
|
529
529
|
return super().changelist_view(request, extra_context)
|
|
@@ -548,7 +548,7 @@ class PydanticAdminMixin:
|
|
|
548
548
|
|
|
549
549
|
# Add Mermaid resources if plugins enabled
|
|
550
550
|
if doc_config.enable_plugins:
|
|
551
|
-
from django_cfg.modules.django_admin.utils.mermaid_plugin import get_mermaid_resources
|
|
551
|
+
from django_cfg.modules.django_admin.utils.markdown.mermaid_plugin import get_mermaid_resources
|
|
552
552
|
extra_context['mermaid_resources'] = get_mermaid_resources()
|
|
553
553
|
|
|
554
554
|
return super().changeform_view(request, object_id, form_url, extra_context)
|
|
@@ -14,6 +14,7 @@ from .field_config import (
|
|
|
14
14
|
DateTimeField,
|
|
15
15
|
ImageField,
|
|
16
16
|
MarkdownField,
|
|
17
|
+
ShortUUIDField,
|
|
17
18
|
TextField,
|
|
18
19
|
UserField,
|
|
19
20
|
)
|
|
@@ -36,6 +37,7 @@ __all__ = [
|
|
|
36
37
|
"DateTimeField",
|
|
37
38
|
"ImageField",
|
|
38
39
|
"MarkdownField",
|
|
40
|
+
"ShortUUIDField",
|
|
39
41
|
"TextField",
|
|
40
42
|
"UserField",
|
|
41
43
|
]
|
|
@@ -341,3 +341,27 @@ class MarkdownField(FieldConfig):
|
|
|
341
341
|
config['header_icon'] = self.header_icon
|
|
342
342
|
|
|
343
343
|
return config
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class ShortUUIDField(FieldConfig):
|
|
347
|
+
"""
|
|
348
|
+
Short UUID widget configuration for displaying shortened UUIDs.
|
|
349
|
+
|
|
350
|
+
Examples:
|
|
351
|
+
ShortUUIDField(name="id", length=8)
|
|
352
|
+
ShortUUIDField(name="uuid", length=12, copy_on_click=True)
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
ui_widget: Literal["short_uuid"] = "short_uuid"
|
|
356
|
+
|
|
357
|
+
length: int = Field(8, description="Number of characters to display from UUID")
|
|
358
|
+
copy_on_click: bool = Field(True, description="Enable click-to-copy functionality")
|
|
359
|
+
show_full_on_hover: bool = Field(True, description="Show full UUID in tooltip on hover")
|
|
360
|
+
|
|
361
|
+
def get_widget_config(self) -> Dict[str, Any]:
|
|
362
|
+
"""Extract short UUID widget configuration."""
|
|
363
|
+
config = super().get_widget_config()
|
|
364
|
+
config['length'] = self.length
|
|
365
|
+
config['copy_on_click'] = self.copy_on_click
|
|
366
|
+
config['show_full_on_hover'] = self.show_full_on_hover
|
|
367
|
+
return config
|
|
@@ -1,17 +1,46 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Display and
|
|
2
|
+
Display and utility modules for Django Admin.
|
|
3
|
+
|
|
4
|
+
Refactored structure with logical organization:
|
|
5
|
+
- badges/ - Status badges, progress badges, counter badges
|
|
6
|
+
- displays/ - User displays, money displays, datetime displays
|
|
7
|
+
- html/ - HTML building utilities (organized by functionality)
|
|
8
|
+
- markdown/ - Markdown rendering with Mermaid support
|
|
9
|
+
- decorators.py - Admin field decorators
|
|
10
|
+
- html_builder.py - Backward-compatible facade for self.html.* API
|
|
3
11
|
"""
|
|
4
12
|
|
|
13
|
+
# Badges
|
|
5
14
|
from .badges import CounterBadge, ProgressBadge, StatusBadge
|
|
15
|
+
|
|
16
|
+
# Decorators
|
|
6
17
|
from .decorators import (
|
|
7
18
|
annotated_field,
|
|
8
19
|
badge_field,
|
|
9
20
|
computed_field,
|
|
10
21
|
currency_field,
|
|
11
22
|
)
|
|
23
|
+
|
|
24
|
+
# Displays
|
|
12
25
|
from .displays import DateTimeDisplay, MoneyDisplay, UserDisplay
|
|
26
|
+
|
|
27
|
+
# HTML Builders (organized by functionality)
|
|
28
|
+
from .html import (
|
|
29
|
+
BadgeElements,
|
|
30
|
+
BaseElements,
|
|
31
|
+
CodeElements,
|
|
32
|
+
CompositionElements,
|
|
33
|
+
FormattingElements,
|
|
34
|
+
KeyValueElements,
|
|
35
|
+
MarkdownIntegration,
|
|
36
|
+
ProgressElements,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# HtmlBuilder - Backward-compatible facade
|
|
13
40
|
from .html_builder import HtmlBuilder
|
|
14
|
-
|
|
41
|
+
|
|
42
|
+
# Markdown
|
|
43
|
+
from .markdown import MarkdownRenderer
|
|
15
44
|
|
|
16
45
|
__all__ = [
|
|
17
46
|
# Display utilities
|
|
@@ -22,7 +51,16 @@ __all__ = [
|
|
|
22
51
|
"StatusBadge",
|
|
23
52
|
"ProgressBadge",
|
|
24
53
|
"CounterBadge",
|
|
25
|
-
# HTML
|
|
54
|
+
# HTML Builders (modular)
|
|
55
|
+
"BaseElements",
|
|
56
|
+
"CodeElements",
|
|
57
|
+
"BadgeElements",
|
|
58
|
+
"CompositionElements",
|
|
59
|
+
"FormattingElements",
|
|
60
|
+
"KeyValueElements",
|
|
61
|
+
"ProgressElements",
|
|
62
|
+
"MarkdownIntegration",
|
|
63
|
+
# HTML Builder (backward-compatible facade)
|
|
26
64
|
"HtmlBuilder",
|
|
27
65
|
# Markdown Renderer
|
|
28
66
|
"MarkdownRenderer",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Badge utilities for Django Admin.
|
|
3
|
+
|
|
4
|
+
Provides StatusBadge, ProgressBadge, and CounterBadge classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .status_badges import CounterBadge, ProgressBadge, StatusBadge
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"StatusBadge",
|
|
11
|
+
"ProgressBadge",
|
|
12
|
+
"CounterBadge",
|
|
13
|
+
]
|
|
@@ -9,9 +9,9 @@ from django.contrib.humanize.templatetags.humanize import intcomma
|
|
|
9
9
|
from django.utils.html import escape, format_html
|
|
10
10
|
from django.utils.safestring import SafeString
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
12
|
+
from ...icons import Icons
|
|
13
|
+
from ...models.badge_models import StatusBadgeConfig
|
|
14
|
+
from ...models.base import BadgeVariant
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Display utilities for Django Admin.
|
|
3
|
+
|
|
4
|
+
Provides UserDisplay, MoneyDisplay, and DateTimeDisplay classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .data_displays import DateTimeDisplay, MoneyDisplay, UserDisplay
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"UserDisplay",
|
|
11
|
+
"MoneyDisplay",
|
|
12
|
+
"DateTimeDisplay",
|
|
13
|
+
]
|
|
@@ -12,8 +12,8 @@ from django.utils import timezone
|
|
|
12
12
|
from django.utils.html import escape, format_html
|
|
13
13
|
from django.utils.safestring import SafeString
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
15
|
+
from ...icons import Icons
|
|
16
|
+
from ...models.display_models import DateTimeDisplayConfig, MoneyDisplayConfig, UserDisplayConfig
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTML builder module - organized and modular.
|
|
3
|
+
|
|
4
|
+
Exports all HTML building classes for easy access.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .badges import BadgeElements
|
|
8
|
+
from .base import BaseElements
|
|
9
|
+
from .code import CodeElements
|
|
10
|
+
from .composition import CompositionElements
|
|
11
|
+
from .formatting import FormattingElements
|
|
12
|
+
from .keyvalue import KeyValueElements
|
|
13
|
+
from .markdown_integration import MarkdownIntegration
|
|
14
|
+
from .progress import ProgressElements
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
# Core elements
|
|
18
|
+
"BaseElements",
|
|
19
|
+
"CodeElements",
|
|
20
|
+
"BadgeElements",
|
|
21
|
+
"CompositionElements",
|
|
22
|
+
"FormattingElements",
|
|
23
|
+
"KeyValueElements",
|
|
24
|
+
"ProgressElements",
|
|
25
|
+
"MarkdownIntegration",
|
|
26
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Badge elements for Django Admin.
|
|
3
|
+
|
|
4
|
+
Provides badge rendering with variants and icons.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from django.utils.html import escape, format_html
|
|
10
|
+
from django.utils.safestring import SafeString
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BadgeElements:
|
|
14
|
+
"""Badge display elements."""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def badge(text: any, variant: str = "primary", icon: Optional[str] = None) -> SafeString:
|
|
18
|
+
"""
|
|
19
|
+
Render badge with optional icon.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
text: Badge text
|
|
23
|
+
variant: primary, success, warning, danger, info, secondary
|
|
24
|
+
icon: Optional Material Icon
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
html.badge("Active", variant="success", icon=Icons.CHECK_CIRCLE)
|
|
28
|
+
"""
|
|
29
|
+
variant_classes = {
|
|
30
|
+
'success': 'bg-success-100 text-success-800 dark:bg-success-900 dark:text-success-200',
|
|
31
|
+
'warning': 'bg-warning-100 text-warning-800 dark:bg-warning-900 dark:text-warning-200',
|
|
32
|
+
'danger': 'bg-danger-100 text-danger-800 dark:bg-danger-900 dark:text-danger-200',
|
|
33
|
+
'info': 'bg-info-100 text-info-800 dark:bg-info-900 dark:text-info-200',
|
|
34
|
+
'primary': 'bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-200',
|
|
35
|
+
'secondary': 'bg-base-100 text-font-default-light dark:bg-base-800 dark:text-font-default-dark',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
css_classes = variant_classes.get(variant, variant_classes['primary'])
|
|
39
|
+
|
|
40
|
+
icon_html = ""
|
|
41
|
+
if icon:
|
|
42
|
+
icon_html = format_html('<span class="material-symbols-outlined text-xs mr-1">{}</span>', icon)
|
|
43
|
+
|
|
44
|
+
return format_html(
|
|
45
|
+
'<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium {}">{}{}</span>',
|
|
46
|
+
css_classes, icon_html, escape(str(text))
|
|
47
|
+
)
|