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.

Files changed (182) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +0 -12
  5. django_cfg/apps/dashboard/serializers/activity.py +1 -1
  6. django_cfg/apps/dashboard/services/__init__.py +0 -2
  7. django_cfg/apps/dashboard/services/charts_service.py +4 -3
  8. django_cfg/apps/dashboard/services/statistics_service.py +11 -2
  9. django_cfg/apps/dashboard/services/system_health_service.py +64 -106
  10. django_cfg/apps/dashboard/urls.py +0 -2
  11. django_cfg/apps/dashboard/views/__init__.py +0 -2
  12. django_cfg/apps/dashboard/views/commands_views.py +3 -6
  13. django_cfg/apps/dashboard/views/overview_views.py +14 -13
  14. django_cfg/apps/grpc/__init__.py +9 -0
  15. django_cfg/apps/grpc/admin/__init__.py +11 -0
  16. django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
  17. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  18. django_cfg/apps/grpc/apps.py +28 -0
  19. django_cfg/apps/grpc/auth/__init__.py +9 -0
  20. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  21. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  22. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  23. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  24. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  25. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  26. django_cfg/apps/grpc/management/__init__.py +1 -0
  27. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  28. django_cfg/apps/grpc/managers/__init__.py +10 -0
  29. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  30. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  31. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  32. django_cfg/apps/grpc/models/__init__.py +9 -0
  33. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  34. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  35. django_cfg/apps/grpc/serializers/health.py +18 -0
  36. django_cfg/apps/grpc/serializers/requests.py +18 -0
  37. django_cfg/apps/grpc/serializers/services.py +50 -0
  38. django_cfg/apps/grpc/serializers/stats.py +22 -0
  39. django_cfg/apps/grpc/services/__init__.py +16 -0
  40. django_cfg/apps/grpc/services/base.py +375 -0
  41. django_cfg/apps/grpc/services/discovery.py +415 -0
  42. django_cfg/apps/grpc/urls.py +23 -0
  43. django_cfg/apps/grpc/utils/__init__.py +13 -0
  44. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  45. django_cfg/apps/grpc/views/__init__.py +9 -0
  46. django_cfg/apps/grpc/views/monitoring.py +497 -0
  47. django_cfg/apps/knowbase/apps.py +2 -2
  48. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
  49. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  50. django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
  51. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  52. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  53. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  54. django_cfg/apps/rq/__init__.py +9 -0
  55. django_cfg/apps/rq/apps.py +80 -0
  56. django_cfg/apps/rq/management/__init__.py +1 -0
  57. django_cfg/apps/rq/management/commands/__init__.py +1 -0
  58. django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
  59. django_cfg/apps/rq/management/commands/rqstats.py +33 -0
  60. django_cfg/apps/rq/management/commands/rqworker.py +31 -0
  61. django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
  62. django_cfg/apps/rq/serializers/__init__.py +40 -0
  63. django_cfg/apps/rq/serializers/health.py +60 -0
  64. django_cfg/apps/rq/serializers/job.py +100 -0
  65. django_cfg/apps/rq/serializers/queue.py +80 -0
  66. django_cfg/apps/rq/serializers/schedule.py +178 -0
  67. django_cfg/apps/rq/serializers/testing.py +139 -0
  68. django_cfg/apps/rq/serializers/worker.py +58 -0
  69. django_cfg/apps/rq/services/__init__.py +25 -0
  70. django_cfg/apps/rq/services/config_helper.py +233 -0
  71. django_cfg/apps/rq/services/models/README.md +417 -0
  72. django_cfg/apps/rq/services/models/__init__.py +30 -0
  73. django_cfg/apps/rq/services/models/event.py +123 -0
  74. django_cfg/apps/rq/services/models/job.py +99 -0
  75. django_cfg/apps/rq/services/models/queue.py +92 -0
  76. django_cfg/apps/rq/services/models/worker.py +104 -0
  77. django_cfg/apps/rq/services/rq_converters.py +183 -0
  78. django_cfg/apps/rq/tasks/__init__.py +23 -0
  79. django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
  80. django_cfg/apps/rq/urls.py +54 -0
  81. django_cfg/apps/rq/views/__init__.py +19 -0
  82. django_cfg/apps/rq/views/jobs.py +882 -0
  83. django_cfg/apps/rq/views/monitoring.py +248 -0
  84. django_cfg/apps/rq/views/queues.py +261 -0
  85. django_cfg/apps/rq/views/schedule.py +400 -0
  86. django_cfg/apps/rq/views/testing.py +761 -0
  87. django_cfg/apps/rq/views/workers.py +195 -0
  88. django_cfg/apps/urls.py +13 -8
  89. django_cfg/config.py +106 -0
  90. django_cfg/core/base/config_model.py +16 -26
  91. django_cfg/core/builders/apps_builder.py +7 -11
  92. django_cfg/core/generation/integration_generators/__init__.py +3 -6
  93. django_cfg/core/generation/integration_generators/django_rq.py +80 -0
  94. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  95. django_cfg/core/generation/orchestrator.py +15 -15
  96. django_cfg/core/integration/display/startup.py +6 -20
  97. django_cfg/mixins/__init__.py +2 -0
  98. django_cfg/mixins/superadmin_api.py +59 -0
  99. django_cfg/models/__init__.py +3 -3
  100. django_cfg/models/api/grpc/__init__.py +59 -0
  101. django_cfg/models/api/grpc/config.py +364 -0
  102. django_cfg/models/django/__init__.py +3 -3
  103. django_cfg/models/django/django_rq.py +621 -0
  104. django_cfg/models/django/revolution_legacy.py +1 -1
  105. django_cfg/modules/base.py +19 -6
  106. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  107. django_cfg/modules/django_admin/config/background_task_config.py +4 -4
  108. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  109. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  110. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  111. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  112. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  113. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  114. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  115. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  116. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  117. django_cfg/modules/django_admin/utils/html/composition.py +205 -0
  118. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  119. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  120. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  121. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  122. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  123. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  124. django_cfg/modules/django_unfold/navigation.py +21 -18
  125. django_cfg/pyproject.toml +4 -6
  126. django_cfg/registry/core.py +4 -7
  127. django_cfg/registry/modules.py +6 -0
  128. django_cfg/static/frontend/admin.zip +0 -0
  129. django_cfg/templates/admin/constance/includes/results_list.html +73 -0
  130. django_cfg/templates/admin/index.html +187 -62
  131. django_cfg/templatetags/django_cfg.py +61 -1
  132. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
  133. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
  134. django_cfg/apps/dashboard/permissions.py +0 -48
  135. django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
  136. django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
  137. django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
  138. django_cfg/apps/tasks/__init__.py +0 -64
  139. django_cfg/apps/tasks/admin/__init__.py +0 -4
  140. django_cfg/apps/tasks/admin/task_log.py +0 -265
  141. django_cfg/apps/tasks/apps.py +0 -15
  142. django_cfg/apps/tasks/filters/__init__.py +0 -10
  143. django_cfg/apps/tasks/filters/task_log.py +0 -121
  144. django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
  145. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
  146. django_cfg/apps/tasks/models/__init__.py +0 -4
  147. django_cfg/apps/tasks/models/task_log.py +0 -246
  148. django_cfg/apps/tasks/serializers/__init__.py +0 -28
  149. django_cfg/apps/tasks/serializers/task_log.py +0 -249
  150. django_cfg/apps/tasks/services/__init__.py +0 -10
  151. django_cfg/apps/tasks/services/client/__init__.py +0 -7
  152. django_cfg/apps/tasks/services/client/client.py +0 -234
  153. django_cfg/apps/tasks/services/config_helper.py +0 -63
  154. django_cfg/apps/tasks/services/sync.py +0 -204
  155. django_cfg/apps/tasks/urls.py +0 -16
  156. django_cfg/apps/tasks/views/__init__.py +0 -10
  157. django_cfg/apps/tasks/views/task_log.py +0 -41
  158. django_cfg/apps/tasks/views/task_log_base.py +0 -41
  159. django_cfg/apps/tasks/views/task_log_overview.py +0 -100
  160. django_cfg/apps/tasks/views/task_log_related.py +0 -41
  161. django_cfg/apps/tasks/views/task_log_stats.py +0 -91
  162. django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
  163. django_cfg/core/generation/integration_generators/django_q2.py +0 -133
  164. django_cfg/core/generation/integration_generators/tasks.py +0 -88
  165. django_cfg/models/django/django_q2.py +0 -514
  166. django_cfg/models/tasks/__init__.py +0 -49
  167. django_cfg/models/tasks/backends.py +0 -122
  168. django_cfg/models/tasks/config.py +0 -209
  169. django_cfg/models/tasks/utils.py +0 -162
  170. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  171. django_cfg/modules/django_q2/README.md +0 -140
  172. django_cfg/modules/django_q2/__init__.py +0 -8
  173. django_cfg/modules/django_q2/apps.py +0 -107
  174. django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
  176. /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
  177. /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
  178. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  179. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  180. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
  181. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
  182. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -18,7 +18,7 @@ class BackgroundTaskConfig(BaseModel):
18
18
  ```python
19
19
  background_config = BackgroundTaskConfig(
20
20
  enabled=True,
21
- task_runner='rearq',
21
+ task_runner='django_rq',
22
22
  batch_size=100,
23
23
  timeout=300,
24
24
  )
@@ -35,9 +35,9 @@ class BackgroundTaskConfig(BaseModel):
35
35
  description="Enable background task processing"
36
36
  )
37
37
 
38
- task_runner: Literal['rearq', 'celery', 'django_q', 'sync'] = Field(
39
- 'rearq',
40
- description="Task runner to use for background operations"
38
+ task_runner: Literal['django_rq', 'sync'] = Field(
39
+ 'sync',
40
+ description="Task runner to use for background operations (django_rq or sync)"
41
41
  )
42
42
 
43
43
  batch_size: int = Field(
@@ -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
+ )
@@ -0,0 +1,167 @@
1
+ """
2
+ Basic HTML elements for Django Admin.
3
+
4
+ Provides fundamental HTML building blocks: icons, spans, text, divs, links, and empty placeholders.
5
+ """
6
+
7
+ from typing import Any, Optional
8
+
9
+ from django.utils.html import escape, format_html
10
+ from django.utils.safestring import SafeString
11
+
12
+
13
+ class BaseElements:
14
+ """Basic HTML building blocks."""
15
+
16
+ @staticmethod
17
+ def icon(icon_name: str, size: str = "xs", css_class: str = "") -> SafeString:
18
+ """
19
+ Render Material Icon.
20
+
21
+ Args:
22
+ icon_name: Icon name from Icons class
23
+ size: xs, sm, base, lg, xl
24
+ css_class: Additional CSS classes
25
+ """
26
+ size_classes = {
27
+ 'xs': 'text-xs',
28
+ 'sm': 'text-sm',
29
+ 'base': 'text-base',
30
+ 'lg': 'text-lg',
31
+ 'xl': 'text-xl'
32
+ }
33
+ size_class = size_classes.get(size, 'text-xs')
34
+ classes = f"material-symbols-outlined {size_class}"
35
+ if css_class:
36
+ classes += f" {css_class}"
37
+
38
+ return format_html('<span class="{}">{}</span>', classes, icon_name)
39
+
40
+ @staticmethod
41
+ def span(text: Any, css_class: str = "") -> SafeString:
42
+ """
43
+ Render text in span with optional CSS class.
44
+
45
+ Args:
46
+ text: Text to display
47
+ css_class: CSS classes
48
+ """
49
+ if css_class:
50
+ return format_html('<span class="{}">{}</span>', css_class, escape(str(text)))
51
+ return format_html('<span>{}</span>', escape(str(text)))
52
+
53
+ @staticmethod
54
+ def text(
55
+ content: Any,
56
+ variant: Optional[str] = None,
57
+ size: Optional[str] = None,
58
+ weight: Optional[str] = None,
59
+ muted: bool = False
60
+ ) -> SafeString:
61
+ """
62
+ Render styled text with semantic variants.
63
+
64
+ Args:
65
+ content: Text content (can be SafeString from other html methods)
66
+ variant: Color variant - 'success', 'warning', 'danger', 'info', 'primary'
67
+ size: Size - 'xs', 'sm', 'base', 'lg', 'xl', '2xl'
68
+ weight: Font weight - 'normal', 'medium', 'semibold', 'bold'
69
+ muted: Use muted/subtle color
70
+
71
+ Usage:
72
+ # Success text
73
+ html.text("$1,234.56", variant="success", size="lg")
74
+
75
+ # Muted small text
76
+ html.text("(12.5%)", muted=True, size="sm")
77
+
78
+ # Combined with other methods
79
+ total = html.number(1234.56, prefix="$")
80
+ html.text(total, variant="success", size="lg")
81
+
82
+ Returns:
83
+ SafeString with styled text
84
+ """
85
+ classes = []
86
+
87
+ # Variant colors
88
+ if variant:
89
+ variant_classes = {
90
+ 'success': 'text-success-600 dark:text-success-400',
91
+ 'warning': 'text-warning-600 dark:text-warning-400',
92
+ 'danger': 'text-danger-600 dark:text-danger-400',
93
+ 'info': 'text-info-600 dark:text-info-400',
94
+ 'primary': 'text-primary-600 dark:text-primary-400',
95
+ }
96
+ classes.append(variant_classes.get(variant, ''))
97
+
98
+ # Muted
99
+ if muted:
100
+ classes.append('text-font-subtle-light dark:text-font-subtle-dark')
101
+
102
+ # Size
103
+ if size:
104
+ size_classes = {
105
+ 'xs': 'text-xs',
106
+ 'sm': 'text-sm',
107
+ 'base': 'text-base',
108
+ 'lg': 'text-lg',
109
+ 'xl': 'text-xl',
110
+ '2xl': 'text-2xl',
111
+ }
112
+ classes.append(size_classes.get(size, ''))
113
+
114
+ # Weight
115
+ if weight:
116
+ weight_classes = {
117
+ 'normal': 'font-normal',
118
+ 'medium': 'font-medium',
119
+ 'semibold': 'font-semibold',
120
+ 'bold': 'font-bold',
121
+ }
122
+ classes.append(weight_classes.get(weight, ''))
123
+
124
+ css_class = ' '.join(filter(None, classes))
125
+
126
+ if css_class:
127
+ return format_html('<span class="{}">{}</span>', css_class, content)
128
+ return format_html('<span>{}</span>', content)
129
+
130
+ @staticmethod
131
+ def div(content: Any, css_class: str = "") -> SafeString:
132
+ """
133
+ Render content in div with optional CSS class.
134
+
135
+ Args:
136
+ content: Content to display (can be SafeString)
137
+ css_class: CSS classes
138
+ """
139
+ if css_class:
140
+ return format_html('<div class="{}">{}</div>', css_class, content)
141
+ return format_html('<div>{}</div>', content)
142
+
143
+ @staticmethod
144
+ def link(url: str, text: str, css_class: str = "", target: str = "") -> SafeString:
145
+ """
146
+ Render link.
147
+
148
+ Args:
149
+ url: URL
150
+ text: Link text
151
+ css_class: CSS classes
152
+ target: Target attribute (_blank, _self, etc)
153
+ """
154
+ if target:
155
+ return format_html(
156
+ '<a href="{}" class="{}" target="{}">{}</a>',
157
+ url, css_class, target, escape(text)
158
+ )
159
+ return format_html('<a href="{}" class="{}">{}</a>', url, css_class, escape(text))
160
+
161
+ @staticmethod
162
+ def empty(text: str = "—") -> SafeString:
163
+ """Render empty/placeholder value."""
164
+ return format_html(
165
+ '<span class="text-font-subtle-light dark:text-font-subtle-dark">{}</span>',
166
+ escape(text)
167
+ )
@@ -0,0 +1,87 @@
1
+ """
2
+ Code display elements for Django Admin.
3
+
4
+ Provides inline code and code block rendering with syntax highlighting support.
5
+ """
6
+
7
+ from typing import Any, Optional
8
+
9
+ from django.utils.html import escape, format_html
10
+ from django.utils.safestring import SafeString
11
+
12
+
13
+ class CodeElements:
14
+ """Code display elements."""
15
+
16
+ @staticmethod
17
+ def code(text: Any, css_class: str = "") -> SafeString:
18
+ """
19
+ Render inline code.
20
+
21
+ Args:
22
+ text: Code text
23
+ css_class: Additional CSS classes
24
+
25
+ Usage:
26
+ html.code("/path/to/file")
27
+ html.code("command --arg value")
28
+ """
29
+ base_classes = "font-mono text-xs bg-base-100 dark:bg-base-800 px-1.5 py-0.5 rounded"
30
+ classes = f"{base_classes} {css_class}".strip()
31
+
32
+ return format_html(
33
+ '<code class="{}">{}</code>',
34
+ classes,
35
+ escape(str(text))
36
+ )
37
+
38
+ @staticmethod
39
+ def code_block(
40
+ text: Any,
41
+ language: Optional[str] = None,
42
+ max_height: Optional[str] = None,
43
+ variant: str = "default"
44
+ ) -> SafeString:
45
+ """
46
+ Render code block with optional syntax highlighting and scrolling.
47
+
48
+ Args:
49
+ text: Code content
50
+ language: Programming language (json, python, bash, etc.) - for future syntax highlighting
51
+ max_height: Max height with scrolling (e.g., "400px", "20rem")
52
+ variant: Color variant - default, warning, danger, success, info
53
+
54
+ Usage:
55
+ html.code_block(json.dumps(data, indent=2), language="json")
56
+ html.code_block(stdout, max_height="400px")
57
+ html.code_block(stderr, max_height="400px", variant="warning")
58
+ """
59
+ # Variant-specific styles
60
+ variant_classes = {
61
+ 'default': 'bg-base-50 dark:bg-base-900 border-base-200 dark:border-base-700',
62
+ 'warning': 'bg-warning-50 dark:bg-warning-900/20 border-warning-200 dark:border-warning-700',
63
+ 'danger': 'bg-danger-50 dark:bg-danger-900/20 border-danger-200 dark:border-danger-700',
64
+ 'success': 'bg-success-50 dark:bg-success-900/20 border-success-200 dark:border-success-700',
65
+ 'info': 'bg-info-50 dark:bg-info-900/20 border-info-200 dark:border-info-700',
66
+ }
67
+
68
+ variant_class = variant_classes.get(variant, variant_classes['default'])
69
+
70
+ # Base styles
71
+ base_classes = f"font-mono text-xs whitespace-pre-wrap break-words border rounded-md p-3 {variant_class}"
72
+
73
+ # Add max-height and overflow if specified
74
+ style = ""
75
+ if max_height:
76
+ style = f'style="max-height: {max_height}; overflow-y: auto;"'
77
+
78
+ # Add language class for potential syntax highlighting
79
+ lang_class = f"language-{language}" if language else ""
80
+
81
+ return format_html(
82
+ '<pre class="{} {}" {}><code>{}</code></pre>',
83
+ base_classes,
84
+ lang_class,
85
+ style,
86
+ escape(str(text))
87
+ )
@@ -0,0 +1,205 @@
1
+ """
2
+ Composition elements for Django Admin.
3
+
4
+ Provides methods for composing multiple elements together: inline, icon_text, header.
5
+ """
6
+
7
+ from typing import Any, Optional, Union
8
+
9
+ from django.utils.html import escape, format_html
10
+ from django.utils.safestring import SafeString
11
+
12
+
13
+ class CompositionElements:
14
+ """Element composition utilities."""
15
+
16
+ @staticmethod
17
+ def icon(icon_name: str, size: str = "xs", css_class: str = "") -> SafeString:
18
+ """
19
+ Render Material Icon (helper for internal use).
20
+
21
+ Args:
22
+ icon_name: Icon name
23
+ size: Icon size
24
+ css_class: Additional CSS classes
25
+ """
26
+ from .base import BaseElements
27
+ return BaseElements.icon(icon_name, size, css_class)
28
+
29
+ @staticmethod
30
+ def icon_text(icon_or_text: Union[str, Any], text: Any = None,
31
+ icon_size: str = "xs", separator: str = " ") -> SafeString:
32
+ """
33
+ Render icon with text or emoji with text.
34
+
35
+ Args:
36
+ icon_or_text: Icon from Icons class, emoji, or text if text param is None
37
+ text: Optional text to display after icon
38
+ icon_size: Icon size (xs, sm, base, lg, xl)
39
+ separator: Separator between icon and text
40
+
41
+ Usage:
42
+ html.icon_text(Icons.EDIT, 5) # Icon with number
43
+ html.icon_text("📝", 5) # Emoji with number
44
+ html.icon_text("Active") # Just text
45
+ """
46
+ if text is None:
47
+ # Just text
48
+ return format_html('<span>{}</span>', escape(str(icon_or_text)))
49
+
50
+ # Check if it's a Material Icon (from Icons class) or emoji
51
+ icon_str = str(icon_or_text)
52
+
53
+ # Detect if it's emoji by checking for non-ASCII characters
54
+ is_emoji = any(ord(c) > 127 for c in icon_str)
55
+
56
+ if is_emoji or icon_str in ['📝', '💬', '🛒', '👤', '📧', '🔔', '⚙️', '🔧', '📊', '🎯']:
57
+ # Emoji
58
+ icon_html = escape(icon_str)
59
+ else:
60
+ # Material Icon
61
+ icon_html = CompositionElements.icon(icon_str, size=icon_size)
62
+
63
+ # DON'T escape SafeString - it's already safe HTML!
64
+ from django.utils.safestring import SafeString
65
+ if isinstance(text, SafeString):
66
+ text_html = text
67
+ else:
68
+ text_html = escape(str(text))
69
+
70
+ return format_html('{}{}<span>{}</span>', icon_html, separator, text_html)
71
+
72
+ @staticmethod
73
+ def inline(*items, separator: str = " | ",
74
+ size: str = "small", css_class: str = "") -> SafeString:
75
+ """
76
+ Render items inline with separator.
77
+
78
+ Args:
79
+ *items: Variable number of SafeString/str items to join (filters out None values)
80
+ separator: Separator between items
81
+ size: small, medium, large
82
+ css_class: Additional CSS classes
83
+
84
+ Usage:
85
+ html.inline(
86
+ html.icon_text(Icons.EDIT, 5),
87
+ html.icon_text(Icons.CHAT, 10),
88
+ separator=" | "
89
+ )
90
+ """
91
+ # Filter out None values
92
+ filtered_items = [item for item in items if item is not None]
93
+
94
+ if not filtered_items:
95
+ return format_html('<span class="text-font-subtle-light dark:text-font-subtle-dark">—</span>')
96
+
97
+ size_classes = {
98
+ 'small': 'text-xs',
99
+ 'medium': 'text-sm',
100
+ 'large': 'text-base'
101
+ }
102
+ size_class = size_classes.get(size, 'text-xs')
103
+
104
+ classes = size_class
105
+ if css_class:
106
+ classes += f" {css_class}"
107
+
108
+ # Convert items to strings, keeping SafeString as-is
109
+ from django.utils.safestring import SafeString, mark_safe
110
+ processed_items = []
111
+ for item in filtered_items:
112
+ if isinstance(item, (SafeString, str)):
113
+ processed_items.append(item)
114
+ else:
115
+ processed_items.append(escape(str(item)))
116
+
117
+ # Join with separator - str() doesn't lose SafeString when joined then mark_safe'd
118
+ joined = mark_safe(separator.join(str(item) for item in processed_items))
119
+
120
+ return format_html('<span class="{}">{}</span>', classes, joined)
121
+
122
+ @staticmethod
123
+ def header(
124
+ title: str,
125
+ subtitle: Optional[str] = None,
126
+ initials: Optional[str] = None,
127
+ avatar_variant: str = "primary"
128
+ ) -> SafeString:
129
+ """
130
+ Render header with avatar/initials, title and subtitle.
131
+
132
+ Creates a horizontal layout with circular avatar badge and text content.
133
+ Common pattern for displaying users, accounts, entities with identity.
134
+
135
+ Args:
136
+ title: Main title text
137
+ subtitle: Optional subtitle text (smaller, muted)
138
+ initials: Optional initials for avatar (e.g., "AB", "JD")
139
+ avatar_variant: Color variant for avatar badge (primary, success, info, etc.)
140
+
141
+ Usage:
142
+ # User with avatar
143
+ html.header(
144
+ title="John Doe",
145
+ subtitle="john@example.com • Admin",
146
+ initials="JD"
147
+ )
148
+
149
+ # Account with info
150
+ html.header(
151
+ title="Trading Account",
152
+ subtitle="Binance • SPOT • user@email.com",
153
+ initials="TA",
154
+ avatar_variant="success"
155
+ )
156
+
157
+ # Simple title only
158
+ html.header(title="Item Name")
159
+
160
+ Returns:
161
+ SafeString with header component HTML
162
+ """
163
+ # Avatar/initials badge
164
+ avatar_html = ""
165
+ if initials:
166
+ avatar_html = format_html(
167
+ '<span class="inline-flex items-center justify-center rounded-full w-8 h-8 text-xs font-semibold {} mr-3">{}</span>',
168
+ CompositionElements._get_avatar_classes(avatar_variant),
169
+ escape(initials)
170
+ )
171
+
172
+ # Title
173
+ title_html = format_html(
174
+ '<div class="font-medium text-sm text-font-default-light dark:text-font-default-dark">{}</div>',
175
+ escape(title)
176
+ )
177
+
178
+ # Subtitle
179
+ subtitle_html = ""
180
+ if subtitle:
181
+ subtitle_html = format_html(
182
+ '<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-0.5">{}</div>',
183
+ escape(subtitle)
184
+ )
185
+
186
+ # Combine
187
+ return format_html(
188
+ '<div class="flex items-center">{}<div>{}{}</div></div>',
189
+ avatar_html,
190
+ title_html,
191
+ subtitle_html
192
+ )
193
+
194
+ @staticmethod
195
+ def _get_avatar_classes(variant: str) -> str:
196
+ """Get CSS classes for avatar badge variant."""
197
+ variant_classes = {
198
+ 'success': 'bg-success-100 text-success-800 dark:bg-success-900 dark:text-success-200',
199
+ 'warning': 'bg-warning-100 text-warning-800 dark:bg-warning-900 dark:text-warning-200',
200
+ 'danger': 'bg-danger-100 text-danger-800 dark:bg-danger-900 dark:text-danger-200',
201
+ 'info': 'bg-info-100 text-info-800 dark:bg-info-900 dark:text-info-200',
202
+ 'primary': 'bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-200',
203
+ 'secondary': 'bg-base-100 text-font-default-light dark:bg-base-800 dark:text-font-default-dark',
204
+ }
205
+ return variant_classes.get(variant, variant_classes['primary'])