django-cfg 1.4.108__py3-none-any.whl → 1.4.110__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.
- django_cfg/__init__.py +1 -1
- django_cfg/modules/django_admin/__init__.py +6 -0
- django_cfg/modules/django_admin/base/pydantic_admin.py +91 -0
- django_cfg/modules/django_admin/config/__init__.py +5 -0
- django_cfg/modules/django_admin/config/admin_config.py +7 -0
- django_cfg/modules/django_admin/config/documentation_config.py +406 -0
- django_cfg/modules/django_admin/config/field_config.py +87 -0
- django_cfg/modules/django_admin/templates/django_admin/change_form_docs.html +23 -0
- django_cfg/modules/django_admin/templates/django_admin/change_list_docs.html +23 -0
- django_cfg/modules/django_admin/templates/django_admin/documentation_block.html +297 -0
- django_cfg/modules/django_admin/templates/django_admin/markdown_docs_block.html +37 -0
- django_cfg/modules/django_admin/utils/__init__.py +3 -0
- django_cfg/modules/django_admin/utils/html_builder.py +94 -1
- django_cfg/modules/django_admin/utils/markdown_renderer.py +344 -0
- django_cfg/pyproject.toml +2 -2
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/METADATA +2 -1
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/RECORD +21 -28
- django_cfg/modules/django_admin/CHANGELOG_CODE_METHODS.md +0 -153
- django_cfg/modules/django_admin/IMPORT_EXPORT_FIX.md +0 -72
- django_cfg/modules/django_admin/RESOURCE_CONFIG_ENHANCEMENT.md +0 -350
- django_cfg/modules/django_client/system/__init__.py +0 -24
- django_cfg/modules/django_client/system/base_generator.py +0 -123
- django_cfg/modules/django_client/system/generate_mjs_clients.py +0 -176
- django_cfg/modules/django_client/system/mjs_generator.py +0 -219
- django_cfg/modules/django_client/system/schema_parser.py +0 -199
- django_cfg/modules/django_client/system/templates/api_client.js.j2 +0 -87
- django_cfg/modules/django_client/system/templates/app_index.js.j2 +0 -13
- django_cfg/modules/django_client/system/templates/base_client.js.j2 +0 -166
- django_cfg/modules/django_client/system/templates/main_index.js.j2 +0 -80
- django_cfg/modules/django_client/system/templates/types.js.j2 +0 -24
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/licenses/LICENSE +0 -0
|
@@ -254,3 +254,90 @@ class ImageField(FieldConfig):
|
|
|
254
254
|
config['caption_template'] = self.caption_template
|
|
255
255
|
config['alt_text'] = self.alt_text
|
|
256
256
|
return config
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class MarkdownField(FieldConfig):
|
|
260
|
+
"""
|
|
261
|
+
Markdown documentation widget configuration.
|
|
262
|
+
|
|
263
|
+
Renders markdown content from field value or external file with beautiful styling.
|
|
264
|
+
Auto-detects whether content is a file path or markdown string.
|
|
265
|
+
|
|
266
|
+
Examples:
|
|
267
|
+
# From model field (markdown string)
|
|
268
|
+
MarkdownField(
|
|
269
|
+
name="description",
|
|
270
|
+
title="Documentation",
|
|
271
|
+
collapsible=True
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# From file path field
|
|
275
|
+
MarkdownField(
|
|
276
|
+
name="docs_path",
|
|
277
|
+
title="User Guide",
|
|
278
|
+
collapsible=True,
|
|
279
|
+
default_open=True
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Static file (same for all objects)
|
|
283
|
+
MarkdownField(
|
|
284
|
+
name="static_doc", # method that returns file path
|
|
285
|
+
title="API Documentation",
|
|
286
|
+
source_file="docs/api.md", # relative to app root
|
|
287
|
+
max_height="500px"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Dynamic markdown with custom title
|
|
291
|
+
MarkdownField(
|
|
292
|
+
name="get_help_text", # method that generates markdown
|
|
293
|
+
title="Help",
|
|
294
|
+
collapsible=True,
|
|
295
|
+
enable_plugins=True
|
|
296
|
+
)
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
ui_widget: Literal["markdown"] = "markdown"
|
|
300
|
+
|
|
301
|
+
# Display options
|
|
302
|
+
collapsible: bool = Field(True, description="Wrap in collapsible details/summary")
|
|
303
|
+
default_open: bool = Field(False, description="Open by default if collapsible")
|
|
304
|
+
max_height: Optional[str] = Field("500px", description="Max height with scrolling (e.g., '500px', None for no limit)")
|
|
305
|
+
|
|
306
|
+
# Content source
|
|
307
|
+
source_file: Optional[str] = Field(
|
|
308
|
+
None,
|
|
309
|
+
description="Static file path relative to app root (e.g., 'docs/api.md')"
|
|
310
|
+
)
|
|
311
|
+
source_field: Optional[str] = Field(
|
|
312
|
+
None,
|
|
313
|
+
description="Alternative field name for content (defaults to 'name' field)"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Markdown options
|
|
317
|
+
enable_plugins: bool = Field(
|
|
318
|
+
True,
|
|
319
|
+
description="Enable mistune plugins (tables, strikethrough, task lists, etc.)"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Custom icon for collapsible header
|
|
323
|
+
header_icon: Optional[str] = Field(
|
|
324
|
+
"description",
|
|
325
|
+
description="Material icon for collapsible header"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def get_widget_config(self) -> Dict[str, Any]:
|
|
329
|
+
"""Extract markdown widget configuration."""
|
|
330
|
+
config = super().get_widget_config()
|
|
331
|
+
config['collapsible'] = self.collapsible
|
|
332
|
+
config['default_open'] = self.default_open
|
|
333
|
+
config['max_height'] = self.max_height
|
|
334
|
+
config['enable_plugins'] = self.enable_plugins
|
|
335
|
+
|
|
336
|
+
if self.source_file is not None:
|
|
337
|
+
config['source_file'] = self.source_file
|
|
338
|
+
if self.source_field is not None:
|
|
339
|
+
config['source_field'] = self.source_field
|
|
340
|
+
if self.header_icon is not None:
|
|
341
|
+
config['header_icon'] = self.header_icon
|
|
342
|
+
|
|
343
|
+
return config
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{% extends "admin/change_form.html" %}
|
|
2
|
+
{% load static %}
|
|
3
|
+
|
|
4
|
+
{% block field_sets %}
|
|
5
|
+
{# Markdown documentation before fieldsets #}
|
|
6
|
+
{% if adminform.model_admin.documentation_config %}
|
|
7
|
+
{% with config=adminform.model_admin.documentation_config %}
|
|
8
|
+
{% if config.show_on_changeform %}
|
|
9
|
+
<div class="mb-6">
|
|
10
|
+
{% include "django_admin/markdown_docs_block.html" with
|
|
11
|
+
content=config.content
|
|
12
|
+
title=config.title
|
|
13
|
+
collapsible=config.collapsible
|
|
14
|
+
default_open=config.default_open
|
|
15
|
+
max_height=config.max_height
|
|
16
|
+
%}
|
|
17
|
+
</div>
|
|
18
|
+
{% endif %}
|
|
19
|
+
{% endwith %}
|
|
20
|
+
{% endif %}
|
|
21
|
+
|
|
22
|
+
{{ block.super }}
|
|
23
|
+
{% endblock %}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
|
2
|
+
{% load static %}
|
|
3
|
+
|
|
4
|
+
{% block content_title %}
|
|
5
|
+
{{ block.super }}
|
|
6
|
+
|
|
7
|
+
{# Markdown documentation above the list #}
|
|
8
|
+
{% if cl.model_admin.documentation_config %}
|
|
9
|
+
{% with config=cl.model_admin.documentation_config %}
|
|
10
|
+
{% if config.show_on_changelist %}
|
|
11
|
+
<div class="mt-4">
|
|
12
|
+
{% include "django_admin/markdown_docs_block.html" with
|
|
13
|
+
content=config.content
|
|
14
|
+
title=config.title
|
|
15
|
+
collapsible=config.collapsible
|
|
16
|
+
default_open=config.default_open
|
|
17
|
+
max_height=config.max_height
|
|
18
|
+
%}
|
|
19
|
+
</div>
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% endwith %}
|
|
22
|
+
{% endif %}
|
|
23
|
+
{% endblock %}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
{% load static %}
|
|
2
|
+
{# Multi-Section Markdown Documentation Block with Management Commands - Used with unfold hooks #}
|
|
3
|
+
|
|
4
|
+
{% if documentation_config and documentation_sections or management_commands %}
|
|
5
|
+
<div class="mb-6">
|
|
6
|
+
<div class="{% if not is_popup %}max-w-full{% endif %}">
|
|
7
|
+
{# Main documentation container with unfold semantic classes #}
|
|
8
|
+
<div class="bg-base-50 dark:bg-base-950 border border-base-200 dark:border-base-700 rounded-default shadow-xs overflow-hidden">
|
|
9
|
+
|
|
10
|
+
{# Header with semantic font colors #}
|
|
11
|
+
<div class="px-5 py-4 border-b border-base-200 dark:border-base-800 bg-base-100 dark:bg-base-900">
|
|
12
|
+
<div class="flex items-center gap-3">
|
|
13
|
+
<span class="material-symbols-outlined text-xl text-primary-600 dark:text-primary-400">description</span>
|
|
14
|
+
<h2 class="text-base font-semibold text-font-important-light dark:text-font-important-dark m-0">
|
|
15
|
+
{{ documentation_config.title }}
|
|
16
|
+
</h2>
|
|
17
|
+
{% if documentation_sections %}
|
|
18
|
+
<span class="ml-auto text-xs font-medium text-font-subtle-light dark:text-font-subtle-dark px-2.5 py-1 bg-base-100 dark:bg-base-800 rounded-full">
|
|
19
|
+
{{ documentation_sections|length }} section{{ documentation_sections|length|pluralize }}
|
|
20
|
+
</span>
|
|
21
|
+
{% endif %}
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
{# Management Commands Section (if any) #}
|
|
26
|
+
{% if management_commands %}
|
|
27
|
+
<details class="group border-b border-base-200 dark:border-base-800">
|
|
28
|
+
<summary class="cursor-pointer px-5 py-3.5 bg-white dark:bg-base-900 hover:bg-base-700/[.04] dark:hover:bg-white/[.04] transition-all duration-200 flex items-center gap-3 select-none list-none">
|
|
29
|
+
{# Terminal icon #}
|
|
30
|
+
<span class="material-symbols-outlined text-base text-font-default-light dark:text-font-default-dark group-open:rotate-90 group-open:text-primary-600 dark:group-open:text-primary-400 transition-all duration-200">
|
|
31
|
+
terminal
|
|
32
|
+
</span>
|
|
33
|
+
|
|
34
|
+
{# Title #}
|
|
35
|
+
<span class="text-sm font-medium text-font-default-light dark:text-font-default-dark flex-1">
|
|
36
|
+
Management Commands
|
|
37
|
+
</span>
|
|
38
|
+
|
|
39
|
+
{# Badge #}
|
|
40
|
+
<span class="text-xs font-medium text-font-subtle-light dark:text-font-subtle-dark px-2.5 py-1 bg-primary-50 dark:bg-primary-900/20 text-primary-600 dark:text-primary-400 rounded-full">
|
|
41
|
+
{{ management_commands|length }} command{{ management_commands|length|pluralize }}
|
|
42
|
+
</span>
|
|
43
|
+
|
|
44
|
+
{# Expand/collapse hint #}
|
|
45
|
+
<span class="text-xs text-font-subtle-light dark:text-font-subtle-dark group-open:hidden">
|
|
46
|
+
Click to expand
|
|
47
|
+
</span>
|
|
48
|
+
<span class="text-xs text-font-subtle-light dark:text-font-subtle-dark hidden group-open:inline">
|
|
49
|
+
Click to collapse
|
|
50
|
+
</span>
|
|
51
|
+
</summary>
|
|
52
|
+
|
|
53
|
+
{# Commands list #}
|
|
54
|
+
<div class="px-5 py-4 bg-white dark:bg-base-900">
|
|
55
|
+
<div class="flex flex-col gap-3">
|
|
56
|
+
{% for cmd in management_commands %}
|
|
57
|
+
<div class="border border-base-200 dark:border-base-700 rounded-default p-4 bg-base-50 dark:bg-base-950 hover:border-primary-300 dark:hover:border-primary-700 transition-colors duration-200">
|
|
58
|
+
{# Command name #}
|
|
59
|
+
<div class="flex items-start gap-3 mb-2">
|
|
60
|
+
<span class="material-symbols-outlined text-sm text-primary-600 dark:text-primary-400 mt-0.5">code</span>
|
|
61
|
+
<div class="flex-1 min-w-0">
|
|
62
|
+
<code class="text-sm font-mono font-semibold text-font-important-light dark:text-font-important-dark">
|
|
63
|
+
python manage.py {{ cmd.name }}
|
|
64
|
+
</code>
|
|
65
|
+
{% if cmd.help %}
|
|
66
|
+
<p class="text-xs text-font-default-light dark:text-font-default-dark mt-1">
|
|
67
|
+
{{ cmd.help }}
|
|
68
|
+
</p>
|
|
69
|
+
{% endif %}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{# Arguments #}
|
|
74
|
+
{% if cmd.arguments %}
|
|
75
|
+
<div class="mt-3 pt-3 border-t border-base-200 dark:border-base-700">
|
|
76
|
+
<div class="text-xs font-medium text-font-subtle-light dark:text-font-subtle-dark mb-2">Arguments:</div>
|
|
77
|
+
<div class="space-y-1.5">
|
|
78
|
+
{% for arg in cmd.arguments %}
|
|
79
|
+
<div class="flex items-start gap-2 text-xs">
|
|
80
|
+
<code class="font-mono text-primary-600 dark:text-primary-400 font-medium">{{ arg.name }}</code>
|
|
81
|
+
{% if arg.required %}
|
|
82
|
+
<span class="px-1.5 py-0.5 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded text-[10px] font-medium">required</span>
|
|
83
|
+
{% endif %}
|
|
84
|
+
{% if arg.help %}
|
|
85
|
+
<span class="text-font-subtle-light dark:text-font-subtle-dark">- {{ arg.help }}</span>
|
|
86
|
+
{% endif %}
|
|
87
|
+
{% if arg.default %}
|
|
88
|
+
<span class="text-font-subtle-light dark:text-font-subtle-dark ml-auto">default: <code class="text-[10px]">{{ arg.default }}</code></span>
|
|
89
|
+
{% endif %}
|
|
90
|
+
</div>
|
|
91
|
+
{% endfor %}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
{% endif %}
|
|
95
|
+
</div>
|
|
96
|
+
{% endfor %}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</details>
|
|
100
|
+
{% endif %}
|
|
101
|
+
|
|
102
|
+
{# Documentation Sections #}
|
|
103
|
+
{% if documentation_sections %}
|
|
104
|
+
<div class="divide-y divide-base-200 dark:divide-base-800">
|
|
105
|
+
{% for section in documentation_sections %}
|
|
106
|
+
{% if documentation_config.collapsible %}
|
|
107
|
+
{# Collapsible section #}
|
|
108
|
+
<details class="group"{% if section.default_open %} open{% endif %}>
|
|
109
|
+
<summary class="cursor-pointer px-5 py-3.5 bg-white dark:bg-base-900 hover:bg-base-700/[.04] dark:hover:bg-white/[.04] transition-all duration-200 flex items-center gap-3 select-none list-none">
|
|
110
|
+
{# Chevron icon #}
|
|
111
|
+
<span class="material-symbols-outlined text-base text-font-default-light dark:text-font-default-dark group-open:rotate-90 group-open:text-primary-600 dark:group-open:text-primary-400 transition-all duration-200">
|
|
112
|
+
chevron_right
|
|
113
|
+
</span>
|
|
114
|
+
|
|
115
|
+
{# Section title #}
|
|
116
|
+
<span class="text-sm font-medium text-font-default-light dark:text-font-default-dark flex-1">
|
|
117
|
+
{{ section.title }}
|
|
118
|
+
</span>
|
|
119
|
+
|
|
120
|
+
{# Expand/collapse hint #}
|
|
121
|
+
<span class="text-xs text-font-subtle-light dark:text-font-subtle-dark group-open:hidden">
|
|
122
|
+
Click to expand
|
|
123
|
+
</span>
|
|
124
|
+
<span class="text-xs text-font-subtle-light dark:text-font-subtle-dark hidden group-open:inline">
|
|
125
|
+
Click to collapse
|
|
126
|
+
</span>
|
|
127
|
+
</summary>
|
|
128
|
+
|
|
129
|
+
{# Section content #}
|
|
130
|
+
<div class="p-5 bg-white dark:bg-base-900">
|
|
131
|
+
<div class="border border-base-200 dark:border-base-700 rounded-default p-4 bg-white dark:bg-base-950 prose prose-sm dark:prose-invert max-w-none overflow-auto"
|
|
132
|
+
{% if documentation_config.max_height %}style="max-height: {{ documentation_config.max_height }};"{% endif %}>
|
|
133
|
+
{{ section.content|safe }}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</details>
|
|
137
|
+
{% else %}
|
|
138
|
+
{# Non-collapsible section #}
|
|
139
|
+
<div class="bg-white dark:bg-base-900">
|
|
140
|
+
{% if section.title != documentation_config.title %}
|
|
141
|
+
<div class="px-5 py-3 bg-base-50 dark:bg-base-950 border-b border-base-200 dark:border-base-800">
|
|
142
|
+
<h3 class="text-sm font-medium text-font-important-light dark:text-font-important-dark m-0">
|
|
143
|
+
{{ section.title }}
|
|
144
|
+
</h3>
|
|
145
|
+
</div>
|
|
146
|
+
{% endif %}
|
|
147
|
+
<div class="p-5">
|
|
148
|
+
<div class="border border-base-200 dark:border-base-700 rounded-default p-4 bg-white dark:bg-base-950 prose prose-sm dark:prose-invert max-w-none overflow-auto"
|
|
149
|
+
{% if documentation_config.max_height %}style="max-height: {{ documentation_config.max_height }};"{% endif %}>
|
|
150
|
+
{{ section.content|safe }}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
{% endif %}
|
|
155
|
+
{% endfor %}
|
|
156
|
+
</div>
|
|
157
|
+
{% endif %}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{# Enhanced dark mode prose styles with unfold semantic colors #}
|
|
163
|
+
<style>
|
|
164
|
+
/* Unfold semantic dark mode prose styles */
|
|
165
|
+
.dark .prose-invert {
|
|
166
|
+
--tw-prose-body: rgb(var(--color-base-300));
|
|
167
|
+
--tw-prose-headings: rgb(var(--color-base-100));
|
|
168
|
+
--tw-prose-lead: rgb(var(--color-base-400));
|
|
169
|
+
--tw-prose-links: rgb(96 165 250);
|
|
170
|
+
--tw-prose-bold: rgb(var(--color-base-100));
|
|
171
|
+
--tw-prose-counters: rgb(var(--color-base-400));
|
|
172
|
+
--tw-prose-bullets: rgb(var(--color-base-500));
|
|
173
|
+
--tw-prose-hr: rgb(var(--color-base-700));
|
|
174
|
+
--tw-prose-quotes: rgb(var(--color-base-100));
|
|
175
|
+
--tw-prose-quote-borders: rgb(var(--color-base-700));
|
|
176
|
+
--tw-prose-captions: rgb(var(--color-base-400));
|
|
177
|
+
--tw-prose-code: rgb(var(--color-base-100));
|
|
178
|
+
--tw-prose-pre-code: rgb(var(--color-base-200));
|
|
179
|
+
--tw-prose-pre-bg: rgb(var(--color-base-950));
|
|
180
|
+
--tw-prose-th-borders: rgb(var(--color-base-700));
|
|
181
|
+
--tw-prose-td-borders: rgb(var(--color-base-700));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Light mode pre blocks */
|
|
185
|
+
.prose pre {
|
|
186
|
+
background-color: rgb(249, 250, 251) !important;
|
|
187
|
+
border: 1px solid rgb(229, 231, 235) !important;
|
|
188
|
+
color: rgb(17, 24, 39) !important;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.prose code {
|
|
192
|
+
background-color: rgb(243, 244, 246) !important;
|
|
193
|
+
color: rgb(17, 24, 39) !important;
|
|
194
|
+
padding: 0.125rem 0.25rem;
|
|
195
|
+
border-radius: 0.25rem;
|
|
196
|
+
font-size: 0.875em;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.prose pre code {
|
|
200
|
+
background-color: transparent !important;
|
|
201
|
+
padding: 0;
|
|
202
|
+
color: inherit !important;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Dark mode pre blocks */
|
|
206
|
+
.dark .prose-invert pre {
|
|
207
|
+
background-color: rgb(3, 7, 18) !important;
|
|
208
|
+
border: 1px solid rgb(55, 65, 81) !important;
|
|
209
|
+
color: rgb(229, 231, 235) !important;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.dark .prose-invert code {
|
|
213
|
+
background-color: rgb(31, 41, 55) !important;
|
|
214
|
+
color: rgb(243, 244, 246) !important;
|
|
215
|
+
padding: 0.125rem 0.25rem;
|
|
216
|
+
border-radius: 0.25rem;
|
|
217
|
+
font-size: 0.875em;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.dark .prose-invert pre code {
|
|
221
|
+
background-color: transparent !important;
|
|
222
|
+
padding: 0;
|
|
223
|
+
color: inherit !important;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Tables with semantic colors */
|
|
227
|
+
.dark .prose-invert table {
|
|
228
|
+
border-color: rgb(var(--color-base-700));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.dark .prose-invert thead {
|
|
232
|
+
border-bottom-color: rgb(var(--color-base-700));
|
|
233
|
+
background-color: rgb(var(--color-base-800));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.dark .prose-invert th,
|
|
237
|
+
.dark .prose-invert td {
|
|
238
|
+
border-color: rgb(var(--color-base-700));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.dark .prose-invert tbody tr {
|
|
242
|
+
border-bottom-color: rgb(var(--color-base-700));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.dark .prose-invert tbody tr:hover {
|
|
246
|
+
background-color: rgb(var(--color-base-800));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Links */
|
|
250
|
+
.dark .prose-invert a {
|
|
251
|
+
color: rgb(96 165 250) !important;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.dark .prose-invert a:hover {
|
|
255
|
+
color: rgb(147 197 253) !important;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Blockquotes with semantic colors */
|
|
259
|
+
.dark .prose-invert blockquote {
|
|
260
|
+
border-left-color: rgb(var(--color-base-700));
|
|
261
|
+
background-color: rgb(var(--color-base-800));
|
|
262
|
+
padding: 1rem;
|
|
263
|
+
border-radius: 0.375rem;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Lists */
|
|
267
|
+
.dark .prose-invert ul > li::marker,
|
|
268
|
+
.dark .prose-invert ol > li::marker {
|
|
269
|
+
color: rgb(var(--color-base-500));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Horizontal rules */
|
|
273
|
+
.dark .prose-invert hr {
|
|
274
|
+
border-color: rgb(var(--color-base-700));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Custom scrollbar with semantic colors */
|
|
278
|
+
.dark .prose-invert::-webkit-scrollbar {
|
|
279
|
+
width: 8px;
|
|
280
|
+
height: 8px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.dark .prose-invert::-webkit-scrollbar-track {
|
|
284
|
+
background: rgb(var(--color-base-800));
|
|
285
|
+
border-radius: 4px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.dark .prose-invert::-webkit-scrollbar-thumb {
|
|
289
|
+
background: rgb(var(--color-base-700));
|
|
290
|
+
border-radius: 4px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.dark .prose-invert::-webkit-scrollbar-thumb:hover {
|
|
294
|
+
background: rgb(var(--color-base-600));
|
|
295
|
+
}
|
|
296
|
+
</style>
|
|
297
|
+
{% endif %}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{% load static %}
|
|
2
|
+
{# Markdown Documentation Block Template #}
|
|
3
|
+
{#
|
|
4
|
+
Usage:
|
|
5
|
+
{% include "django_admin/markdown_docs_block.html" with
|
|
6
|
+
content=markdown_content
|
|
7
|
+
title="Documentation"
|
|
8
|
+
collapsible=True
|
|
9
|
+
default_open=False
|
|
10
|
+
%}
|
|
11
|
+
#}
|
|
12
|
+
|
|
13
|
+
<div class="mb-6">
|
|
14
|
+
{% if collapsible %}
|
|
15
|
+
<details class="group border border-base-200 dark:border-base-700 rounded-lg overflow-hidden bg-white dark:bg-base-800 shadow-sm hover:shadow-md transition-shadow"{% if default_open %} open{% endif %}>
|
|
16
|
+
<summary class="cursor-pointer px-4 py-3 bg-base-50 dark:bg-base-900 hover:bg-base-100 dark:hover:bg-base-800 transition-colors flex items-center gap-2 select-none">
|
|
17
|
+
<span class="material-symbols-outlined text-base text-primary-600 dark:text-primary-400 group-open:rotate-90 transition-transform">chevron_right</span>
|
|
18
|
+
<span class="font-semibold text-sm text-font-default-light dark:text-font-default-dark">{{ title|default:"Documentation" }}</span>
|
|
19
|
+
<span class="text-xs text-font-subtle-light dark:text-font-subtle-dark ml-auto">{% if default_open %}Click to collapse{% else %}Click to expand{% endif %}</span>
|
|
20
|
+
</summary>
|
|
21
|
+
<div class="p-4 bg-white dark:bg-base-800"{% if max_height %} style="max-height: {{ max_height }}; overflow-y: auto;"{% endif %}>
|
|
22
|
+
{{ content|safe }}
|
|
23
|
+
</div>
|
|
24
|
+
</details>
|
|
25
|
+
{% else %}
|
|
26
|
+
<div class="border border-base-200 dark:border-base-700 rounded-lg overflow-hidden bg-white dark:bg-base-800 shadow-sm">
|
|
27
|
+
{% if title %}
|
|
28
|
+
<div class="px-4 py-3 bg-base-50 dark:bg-base-900 border-b border-base-200 dark:border-base-700">
|
|
29
|
+
<h3 class="font-semibold text-sm text-font-default-light dark:text-font-default-dark">{{ title }}</h3>
|
|
30
|
+
</div>
|
|
31
|
+
{% endif %}
|
|
32
|
+
<div class="p-4"{% if max_height %} style="max-height: {{ max_height }}; overflow-y: auto;"{% endif %}>
|
|
33
|
+
{{ content|safe }}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
{% endif %}
|
|
37
|
+
</div>
|
|
@@ -11,6 +11,7 @@ from .decorators import (
|
|
|
11
11
|
)
|
|
12
12
|
from .displays import DateTimeDisplay, MoneyDisplay, UserDisplay
|
|
13
13
|
from .html_builder import HtmlBuilder
|
|
14
|
+
from .markdown_renderer import MarkdownRenderer
|
|
14
15
|
|
|
15
16
|
__all__ = [
|
|
16
17
|
# Display utilities
|
|
@@ -23,6 +24,8 @@ __all__ = [
|
|
|
23
24
|
"CounterBadge",
|
|
24
25
|
# HTML Builder
|
|
25
26
|
"HtmlBuilder",
|
|
27
|
+
# Markdown Renderer
|
|
28
|
+
"MarkdownRenderer",
|
|
26
29
|
# Decorators
|
|
27
30
|
"computed_field",
|
|
28
31
|
"badge_field",
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
Universal HTML builder for Django Admin display methods.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import Any, List, Optional, Union
|
|
6
7
|
|
|
7
8
|
from django.utils.html import escape, format_html
|
|
8
9
|
from django.utils.safestring import SafeString
|
|
9
10
|
|
|
10
11
|
from ..icons import Icons
|
|
12
|
+
from .markdown_renderer import MarkdownRenderer
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class HtmlBuilder:
|
|
14
16
|
"""
|
|
15
|
-
Universal HTML builder with Material Icons support.
|
|
17
|
+
Universal HTML builder with Material Icons support and Markdown rendering.
|
|
16
18
|
|
|
17
19
|
Usage in admin methods:
|
|
18
20
|
def stats(self, obj):
|
|
@@ -20,6 +22,9 @@ class HtmlBuilder:
|
|
|
20
22
|
self.html.icon_text(Icons.EDIT, obj.posts_count),
|
|
21
23
|
self.html.icon_text(Icons.CHAT, obj.comments_count),
|
|
22
24
|
])
|
|
25
|
+
|
|
26
|
+
def documentation(self, obj):
|
|
27
|
+
return self.html.markdown_docs(obj.docs_path)
|
|
23
28
|
"""
|
|
24
29
|
|
|
25
30
|
@staticmethod
|
|
@@ -276,3 +281,91 @@ class HtmlBuilder:
|
|
|
276
281
|
style,
|
|
277
282
|
escape(str(text))
|
|
278
283
|
)
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def markdown(
|
|
287
|
+
text: str,
|
|
288
|
+
css_class: str = "",
|
|
289
|
+
max_height: Optional[str] = None,
|
|
290
|
+
enable_plugins: bool = True
|
|
291
|
+
) -> SafeString:
|
|
292
|
+
"""
|
|
293
|
+
Render markdown text to beautifully styled HTML.
|
|
294
|
+
|
|
295
|
+
Delegates to MarkdownRenderer.render_markdown() for actual rendering.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
text: Markdown content
|
|
299
|
+
css_class: Additional CSS classes
|
|
300
|
+
max_height: Max height with scrolling (e.g., "400px", "20rem")
|
|
301
|
+
enable_plugins: Enable mistune plugins (tables, strikethrough, etc.)
|
|
302
|
+
|
|
303
|
+
Usage:
|
|
304
|
+
# Simple markdown rendering
|
|
305
|
+
html.markdown("# Hello\\n\\nThis is **bold** text")
|
|
306
|
+
|
|
307
|
+
# With custom styling
|
|
308
|
+
html.markdown(obj.description, css_class="my-custom-class")
|
|
309
|
+
|
|
310
|
+
# With max height for long content
|
|
311
|
+
html.markdown(obj.documentation, max_height="500px")
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
SafeString with rendered HTML
|
|
315
|
+
"""
|
|
316
|
+
return MarkdownRenderer.render_markdown(
|
|
317
|
+
text=text,
|
|
318
|
+
css_class=css_class,
|
|
319
|
+
max_height=max_height,
|
|
320
|
+
enable_plugins=enable_plugins
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def markdown_docs(
|
|
325
|
+
content: Union[str, Path],
|
|
326
|
+
collapsible: bool = True,
|
|
327
|
+
title: str = "Documentation",
|
|
328
|
+
icon: str = "description",
|
|
329
|
+
max_height: Optional[str] = "500px",
|
|
330
|
+
enable_plugins: bool = True,
|
|
331
|
+
default_open: bool = False
|
|
332
|
+
) -> SafeString:
|
|
333
|
+
"""
|
|
334
|
+
Render markdown documentation from string or file with collapsible UI.
|
|
335
|
+
|
|
336
|
+
Auto-detects whether content is a file path or markdown string.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
content: Markdown string or path to .md file
|
|
340
|
+
collapsible: Wrap in collapsible details/summary
|
|
341
|
+
title: Title for collapsible section
|
|
342
|
+
icon: Material icon name for title
|
|
343
|
+
max_height: Max height for scrolling
|
|
344
|
+
enable_plugins: Enable markdown plugins
|
|
345
|
+
default_open: Open by default if collapsible
|
|
346
|
+
|
|
347
|
+
Usage:
|
|
348
|
+
# From string with collapse
|
|
349
|
+
html.markdown_docs(obj.description, title="Description")
|
|
350
|
+
|
|
351
|
+
# From file
|
|
352
|
+
html.markdown_docs("docs/api.md", title="API Documentation")
|
|
353
|
+
|
|
354
|
+
# Simple, no collapse
|
|
355
|
+
html.markdown_docs(obj.notes, collapsible=False)
|
|
356
|
+
|
|
357
|
+
# Open by default
|
|
358
|
+
html.markdown_docs(obj.readme, default_open=True)
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Rendered markdown with beautiful Tailwind styling
|
|
362
|
+
"""
|
|
363
|
+
return MarkdownRenderer.render(
|
|
364
|
+
content=content,
|
|
365
|
+
collapsible=collapsible,
|
|
366
|
+
title=title,
|
|
367
|
+
icon=icon,
|
|
368
|
+
max_height=max_height,
|
|
369
|
+
enable_plugins=enable_plugins,
|
|
370
|
+
default_open=default_open
|
|
371
|
+
)
|