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,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
@@ -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 badge utilities for Django Admin.
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
- from .markdown_renderer import MarkdownRenderer
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 Builder
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 ..icons import Icons
13
- from ..models.badge_models import StatusBadgeConfig
14
- from ..models.base import BadgeVariant
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 ..icons import Icons
16
- from ..models.display_models import DateTimeDisplayConfig, MoneyDisplayConfig, UserDisplayConfig
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
+ )