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.
Files changed (34) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/modules/django_admin/__init__.py +6 -0
  3. django_cfg/modules/django_admin/base/pydantic_admin.py +91 -0
  4. django_cfg/modules/django_admin/config/__init__.py +5 -0
  5. django_cfg/modules/django_admin/config/admin_config.py +7 -0
  6. django_cfg/modules/django_admin/config/documentation_config.py +406 -0
  7. django_cfg/modules/django_admin/config/field_config.py +87 -0
  8. django_cfg/modules/django_admin/templates/django_admin/change_form_docs.html +23 -0
  9. django_cfg/modules/django_admin/templates/django_admin/change_list_docs.html +23 -0
  10. django_cfg/modules/django_admin/templates/django_admin/documentation_block.html +297 -0
  11. django_cfg/modules/django_admin/templates/django_admin/markdown_docs_block.html +37 -0
  12. django_cfg/modules/django_admin/utils/__init__.py +3 -0
  13. django_cfg/modules/django_admin/utils/html_builder.py +94 -1
  14. django_cfg/modules/django_admin/utils/markdown_renderer.py +344 -0
  15. django_cfg/pyproject.toml +2 -2
  16. django_cfg/static/frontend/admin.zip +0 -0
  17. {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/METADATA +2 -1
  18. {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/RECORD +21 -28
  19. django_cfg/modules/django_admin/CHANGELOG_CODE_METHODS.md +0 -153
  20. django_cfg/modules/django_admin/IMPORT_EXPORT_FIX.md +0 -72
  21. django_cfg/modules/django_admin/RESOURCE_CONFIG_ENHANCEMENT.md +0 -350
  22. django_cfg/modules/django_client/system/__init__.py +0 -24
  23. django_cfg/modules/django_client/system/base_generator.py +0 -123
  24. django_cfg/modules/django_client/system/generate_mjs_clients.py +0 -176
  25. django_cfg/modules/django_client/system/mjs_generator.py +0 -219
  26. django_cfg/modules/django_client/system/schema_parser.py +0 -199
  27. django_cfg/modules/django_client/system/templates/api_client.js.j2 +0 -87
  28. django_cfg/modules/django_client/system/templates/app_index.js.j2 +0 -13
  29. django_cfg/modules/django_client/system/templates/base_client.js.j2 +0 -166
  30. django_cfg/modules/django_client/system/templates/main_index.js.j2 +0 -80
  31. django_cfg/modules/django_client/system/templates/types.js.j2 +0 -24
  32. {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/WHEEL +0 -0
  33. {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/entry_points.txt +0 -0
  34. {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,344 @@
1
+ """
2
+ Markdown rendering service for Django Admin.
3
+
4
+ Provides utilities for rendering markdown content from strings or files
5
+ with beautiful Tailwind CSS styling and collapsible sections.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Any, Optional, Union
12
+
13
+ from django.utils.html import escape, format_html
14
+ from django.utils.safestring import SafeString, mark_safe
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ try:
19
+ import mistune
20
+ MISTUNE_AVAILABLE = True
21
+ except ImportError:
22
+ MISTUNE_AVAILABLE = False
23
+
24
+
25
+ class MarkdownRenderer:
26
+ """
27
+ Markdown rendering service with file/string support and collapsible UI.
28
+
29
+ Features:
30
+ - Render markdown from strings or .md files
31
+ - Auto-detect content type (file path vs markdown string)
32
+ - Beautiful Tailwind CSS styling with dark mode
33
+ - Collapsible sections for documentation
34
+ - Support for all markdown features (tables, code blocks, etc.)
35
+
36
+ Usage:
37
+ # In admin.py
38
+ from django_cfg.modules.django_admin.utils import MarkdownRenderer
39
+
40
+ class MyAdmin(admin.ModelAdmin):
41
+ def documentation(self, obj):
42
+ # From file
43
+ return MarkdownRenderer.render(obj.docs_file_path, collapsible=True)
44
+
45
+ # From string
46
+ return MarkdownRenderer.render(obj.markdown_content, collapsible=True)
47
+
48
+ # From field or string with custom title
49
+ return MarkdownRenderer.render(
50
+ obj.description,
51
+ collapsible=True,
52
+ title="Description"
53
+ )
54
+ """
55
+
56
+ # Singleton markdown parser instances
57
+ _md_with_plugins = None
58
+ _md_without_plugins = None
59
+
60
+ @classmethod
61
+ def _get_markdown_parser(cls, enable_plugins: bool = True):
62
+ """Get or create markdown parser instance (singleton pattern)."""
63
+ if not MISTUNE_AVAILABLE:
64
+ return None
65
+
66
+ if enable_plugins:
67
+ if cls._md_with_plugins is None:
68
+ cls._md_with_plugins = mistune.create_markdown(
69
+ plugins=['strikethrough', 'table', 'url', 'task_lists', 'def_list']
70
+ )
71
+ return cls._md_with_plugins
72
+ else:
73
+ if cls._md_without_plugins is None:
74
+ cls._md_without_plugins = mistune.create_markdown()
75
+ return cls._md_without_plugins
76
+
77
+ @classmethod
78
+ def _load_from_file(cls, file_path: Union[str, Path]) -> Optional[str]:
79
+ """
80
+ Load markdown content from file.
81
+
82
+ Args:
83
+ file_path: Path to .md file
84
+
85
+ Returns:
86
+ File content or None if error
87
+ """
88
+ try:
89
+ path = Path(file_path)
90
+ if not path.exists():
91
+ logger.warning(f"Markdown file not found: {file_path}")
92
+ return None
93
+
94
+ if not path.suffix.lower() in ['.md', '.markdown']:
95
+ logger.warning(f"File is not a markdown file: {file_path}")
96
+ return None
97
+
98
+ with open(path, 'r', encoding='utf-8') as f:
99
+ return f.read()
100
+ except Exception as e:
101
+ logger.error(f"Error loading markdown file {file_path}: {e}")
102
+ return None
103
+
104
+ @classmethod
105
+ def _is_file_path(cls, content: str) -> bool:
106
+ """
107
+ Check if content is a file path.
108
+
109
+ Args:
110
+ content: String to check
111
+
112
+ Returns:
113
+ True if looks like a file path
114
+ """
115
+ # Check if it's a valid path and file exists
116
+ if '\n' in content:
117
+ return False # Multi-line content is not a file path
118
+
119
+ try:
120
+ path = Path(content)
121
+ return path.exists() and path.is_file() and path.suffix.lower() in ['.md', '.markdown']
122
+ except (OSError, ValueError):
123
+ return False
124
+
125
+ @classmethod
126
+ def render_markdown(
127
+ cls,
128
+ text: str,
129
+ css_class: str = "",
130
+ max_height: Optional[str] = None,
131
+ enable_plugins: bool = True
132
+ ) -> SafeString:
133
+ """
134
+ Render markdown text to HTML with beautiful Tailwind styling.
135
+
136
+ Args:
137
+ text: Markdown content
138
+ css_class: Additional CSS classes
139
+ max_height: Max height with scrolling (e.g., "400px", "20rem")
140
+ enable_plugins: Enable mistune plugins (tables, strikethrough, etc.)
141
+
142
+ Returns:
143
+ SafeString with rendered HTML
144
+ """
145
+ if not MISTUNE_AVAILABLE:
146
+ return format_html(
147
+ '<div class="text-warning-600 dark:text-warning-400 p-3 bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-700 rounded">'
148
+ '<strong>⚠️ Mistune not installed:</strong> Install with: pip install mistune>=3.1.4'
149
+ '</div>'
150
+ )
151
+
152
+ if not text:
153
+ return format_html(
154
+ '<span class="text-font-subtle-light dark:text-font-subtle-dark">No content</span>'
155
+ )
156
+
157
+ # Get markdown parser
158
+ md = cls._get_markdown_parser(enable_plugins)
159
+ if not md:
160
+ return format_html(
161
+ '<div class="text-danger-600 dark:text-danger-400">Error: Could not initialize markdown parser</div>'
162
+ )
163
+
164
+ # Render markdown to HTML
165
+ html_content = md(str(text))
166
+
167
+ # Beautiful Tailwind prose styles
168
+ base_classes = (
169
+ "prose prose-sm dark:prose-invert max-w-none "
170
+ "prose-headings:font-semibold prose-headings:text-font-default-light dark:prose-headings:text-font-default-dark "
171
+ "prose-h1:text-2xl prose-h1:mb-4 prose-h1:mt-6 prose-h1:border-b prose-h1:border-base-200 dark:prose-h1:border-base-700 prose-h1:pb-2 "
172
+ "prose-h2:text-xl prose-h2:mb-3 prose-h2:mt-5 prose-h2:border-b prose-h2:border-base-200 dark:prose-h2:border-base-700 prose-h2:pb-1 "
173
+ "prose-h3:text-lg prose-h3:mb-2 prose-h3:mt-4 "
174
+ "prose-h4:text-base prose-h4:mb-2 prose-h4:mt-3 "
175
+ "prose-p:mb-3 prose-p:leading-relaxed prose-p:text-font-default-light dark:prose-p:text-font-default-dark "
176
+ "prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-a:no-underline hover:prose-a:underline prose-a:font-medium "
177
+ "prose-strong:text-font-default-light dark:prose-strong:text-font-default-dark prose-strong:font-semibold "
178
+ "prose-code:bg-base-100 dark:prose-code:bg-base-800 prose-code:text-danger-600 dark:prose-code:text-danger-400 "
179
+ "prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-xs prose-code:font-mono "
180
+ "prose-code:before:content-none prose-code:after:content-none "
181
+ "prose-pre:bg-base-50 dark:prose-pre:bg-base-900 prose-pre:border prose-pre:border-base-200 dark:prose-pre:border-base-700 prose-pre:rounded-lg "
182
+ "prose-blockquote:border-l-4 prose-blockquote:border-primary-300 dark:prose-blockquote:border-primary-600 "
183
+ "prose-blockquote:bg-base-50 dark:prose-blockquote:bg-base-900 prose-blockquote:pl-4 prose-blockquote:pr-4 prose-blockquote:py-2 "
184
+ "prose-blockquote:italic prose-blockquote:text-font-subtle-light dark:prose-blockquote:text-font-subtle-dark "
185
+ "prose-ul:list-disc prose-ul:pl-6 prose-ul:mb-3 prose-ul:text-font-default-light dark:prose-ul:text-font-default-dark "
186
+ "prose-ol:list-decimal prose-ol:pl-6 prose-ol:mb-3 prose-ol:text-font-default-light dark:prose-ol:text-font-default-dark "
187
+ "prose-li:mb-1 "
188
+ "prose-table:border-collapse prose-table:w-full prose-table:text-sm "
189
+ "prose-th:bg-base-100 dark:prose-th:bg-base-800 prose-th:border prose-th:border-base-300 dark:prose-th:border-base-600 "
190
+ "prose-th:px-4 prose-th:py-2 prose-th:text-left prose-th:font-semibold prose-th:text-font-default-light dark:prose-th:text-font-default-dark "
191
+ "prose-td:border prose-td:border-base-200 dark:prose-td:border-base-700 prose-td:px-4 prose-td:py-2 "
192
+ "prose-td:text-font-default-light dark:prose-td:text-font-default-dark "
193
+ "prose-img:rounded-lg prose-img:shadow-md prose-img:border prose-img:border-base-200 dark:prose-img:border-base-700 "
194
+ "prose-hr:border-base-200 dark:prose-hr:border-base-700 prose-hr:my-6 "
195
+ "prose-em:text-font-default-light dark:prose-em:text-font-default-dark "
196
+ )
197
+
198
+ # Combine with custom classes
199
+ classes = f"{base_classes} {css_class}".strip()
200
+
201
+ # Add container div with max-height if specified
202
+ style = ""
203
+ if max_height:
204
+ style = f'max-height: {max_height}; overflow-y: auto;'
205
+
206
+ return format_html(
207
+ '<div class="{}" {}>{}</div>',
208
+ classes,
209
+ mark_safe(f'style="{style}"') if style else '',
210
+ mark_safe(html_content)
211
+ )
212
+
213
+ @classmethod
214
+ def render(
215
+ cls,
216
+ content: Union[str, Path],
217
+ collapsible: bool = False,
218
+ title: str = "Documentation",
219
+ icon: str = "description",
220
+ max_height: Optional[str] = "500px",
221
+ enable_plugins: bool = True,
222
+ default_open: bool = False
223
+ ) -> SafeString:
224
+ """
225
+ Universal markdown renderer - auto-detects file vs string content.
226
+
227
+ Args:
228
+ content: Markdown string, file path, or Path object
229
+ collapsible: Wrap in collapsible Tailwind details/summary
230
+ title: Title for collapsible section
231
+ icon: Material icon name for title
232
+ max_height: Max height for scrolling (None = no limit)
233
+ enable_plugins: Enable markdown plugins
234
+ default_open: If collapsible, open by default
235
+
236
+ Returns:
237
+ Rendered HTML as SafeString
238
+
239
+ Examples:
240
+ # Simple render from string
241
+ MarkdownRenderer.render("# Hello\\n\\nThis is **bold**")
242
+
243
+ # From file
244
+ MarkdownRenderer.render("/path/to/docs.md")
245
+
246
+ # Collapsible documentation
247
+ MarkdownRenderer.render(
248
+ obj.description,
249
+ collapsible=True,
250
+ title="API Documentation",
251
+ icon="api"
252
+ )
253
+
254
+ # From file with collapse
255
+ MarkdownRenderer.render(
256
+ "docs/README.md",
257
+ collapsible=True,
258
+ title="Project Documentation",
259
+ default_open=True
260
+ )
261
+ """
262
+ if not content:
263
+ return format_html(
264
+ '<span class="text-font-subtle-light dark:text-font-subtle-dark text-sm">No documentation available</span>'
265
+ )
266
+
267
+ # Convert to string if Path object
268
+ content_str = str(content)
269
+
270
+ # Try to load from file if it looks like a file path
271
+ markdown_text = content_str
272
+ is_from_file = False
273
+
274
+ if cls._is_file_path(content_str):
275
+ file_content = cls._load_from_file(content_str)
276
+ if file_content:
277
+ markdown_text = file_content
278
+ is_from_file = True
279
+ else:
280
+ return format_html(
281
+ '<div class="text-warning-600 dark:text-warning-400 p-3 bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-700 rounded">'
282
+ '<strong>⚠️ File not found:</strong> {}'
283
+ '</div>',
284
+ escape(content_str)
285
+ )
286
+
287
+ # Render markdown
288
+ rendered_html = cls.render_markdown(
289
+ markdown_text,
290
+ max_height=max_height if not collapsible else None,
291
+ enable_plugins=enable_plugins
292
+ )
293
+
294
+ # Return without collapse if not requested
295
+ if not collapsible:
296
+ return rendered_html
297
+
298
+ # Wrap in beautiful collapsible section
299
+ open_attr = 'open' if default_open else ''
300
+
301
+ return format_html(
302
+ '<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" {}>'
303
+ '<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">'
304
+ '<span class="material-symbols-outlined text-base text-primary-600 dark:text-primary-400 group-open:rotate-90 transition-transform">{}</span>'
305
+ '<span class="font-semibold text-sm text-font-default-light dark:text-font-default-dark">{}</span>'
306
+ '<span class="text-xs text-font-subtle-light dark:text-font-subtle-dark ml-auto">{}</span>'
307
+ '</summary>'
308
+ '<div class="p-4 bg-white dark:bg-base-800" {}>'
309
+ '{}'
310
+ '</div>'
311
+ '</details>',
312
+ mark_safe(open_attr),
313
+ 'chevron_right', # Arrow icon
314
+ escape(title),
315
+ 'Click to expand' if not default_open else 'Click to collapse',
316
+ mark_safe(f'style="max-height: {max_height}; overflow-y: auto;"') if max_height else '',
317
+ rendered_html
318
+ )
319
+
320
+ @classmethod
321
+ def render_docs(
322
+ cls,
323
+ content: Union[str, Path],
324
+ **kwargs
325
+ ) -> SafeString:
326
+ """
327
+ Shorthand for rendering documentation (always collapsible by default).
328
+
329
+ Args:
330
+ content: Markdown string or file path
331
+ **kwargs: Additional arguments passed to render()
332
+
333
+ Returns:
334
+ Rendered collapsible documentation
335
+
336
+ Example:
337
+ def documentation(self, obj):
338
+ return MarkdownRenderer.render_docs(obj.docs_path)
339
+ """
340
+ # Set collapsible=True by default for docs
341
+ if 'collapsible' not in kwargs:
342
+ kwargs['collapsible'] = True
343
+
344
+ return cls.render(content, **kwargs)
django_cfg/pyproject.toml CHANGED
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "django-cfg"
7
- version = "1.4.108"
7
+ version = "1.4.110"
8
8
  description = "Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features."
9
9
  readme = "README.md"
10
10
  keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "nextjs-admin", "react-admin", "websocket", "centrifugo", "real-time", "typescript-generation", "ai-agents", "enterprise-django", "django-settings", "type-safe-config", "modern-django",]
11
11
  classifiers = [ "Development Status :: 4 - Beta", "Framework :: Django", "Framework :: Django :: 5.2", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration", "Typing :: Typed",]
12
12
  requires-python = ">=3.12,<3.14"
13
- dependencies = [ "pydantic>=2.11.0,<3.0", "pydantic[email]>=2.11.0,<3.0", "PyYAML>=6.0,<7.0", "click>=8.2.0,<9.0", "questionary>=2.1.0,<3.0", "rich>=14.0.0,<15.0", "cloudflare>=4.3.0,<5.0", "loguru>=0.7.0,<1.0", "colorlog>=6.9.0,<7.0", "cachetools>=5.3.0,<7.0", "toml>=0.10.2,<0.11.0", "ngrok>=1.5.1; python_version>='3.12'", "psycopg[binary,pool]>=3.2.0,<4.0", "dj-database-url>=3.0.0,<4.0", "whitenoise>=6.8.0,<7.0", "django-cors-headers>=4.7.0,<5.0", "djangorestframework>=3.16.0,<4.0", "djangorestframework-simplejwt>=5.5.0,<6.0", "djangorestframework-simplejwt[token-blacklist]>=5.5.0,<6.0", "drf-nested-routers>=0.94.0,<1.0", "django-filter>=25.0,<26.0", "django-ratelimit>=4.1.0,<5.0.0", "drf-spectacular>=0.28.0,<1.0", "drf-spectacular-sidecar>=2025.8.0,<2026.0", "django-json-widget>=2.0.0,<3.0", "django-import-export>=4.3.0,<5.0", "django-extensions>=4.1.0,<5.0", "django-constance>=4.3.0,<5.0", "django-unfold>=0.64.0,<1.0", "django-redis>=6.0.0,<7.0", "redis>=6.4.0,<7.0", "hiredis>=2.0.0,<4.0", "rearq>=0.2.0,<1.0", "setuptools>=75.0.0; python_version>='3.13'", "pyTelegramBotAPI>=4.28.0,<5.0", "coolname>=2.2.0,<3.0", "django-admin-rangefilter>=0.13.0,<1.0", "python-json-logger>=3.3.0,<4.0", "requests>=2.32.0,<3.0", "tiktoken>=0.11.0,<1.0", "openai>=1.107.0,<2.0", "twilio>=9.8.0,<10.0", "sendgrid>=6.12.0,<7.0", "beautifulsoup4>=4.13.0,<5.0", "lxml>=6.0.0,<7.0", "pgvector>=0.4.0,<1.0", "tenacity>=9.1.2,<10.0.0", "mypy (>=1.18.2,<2.0.0)", "django-tailwind[reload] (>=4.2.0,<5.0.0)", "jinja2 (>=3.1.6,<4.0.0)", "django-axes[ipware] (>=8.0.0,<9.0.0)", "pydantic-settings (>=2.11.0,<3.0.0)", "pytz>=2025.1", "httpx>=0.28.1,<1.0",]
13
+ dependencies = [ "pydantic>=2.11.0,<3.0", "pydantic[email]>=2.11.0,<3.0", "PyYAML>=6.0,<7.0", "click>=8.2.0,<9.0", "questionary>=2.1.0,<3.0", "rich>=14.0.0,<15.0", "cloudflare>=4.3.0,<5.0", "loguru>=0.7.0,<1.0", "colorlog>=6.9.0,<7.0", "cachetools>=5.3.0,<7.0", "toml>=0.10.2,<0.11.0", "ngrok>=1.5.1; python_version>='3.12'", "psycopg[binary,pool]>=3.2.0,<4.0", "dj-database-url>=3.0.0,<4.0", "whitenoise>=6.8.0,<7.0", "django-cors-headers>=4.7.0,<5.0", "djangorestframework>=3.16.0,<4.0", "djangorestframework-simplejwt>=5.5.0,<6.0", "djangorestframework-simplejwt[token-blacklist]>=5.5.0,<6.0", "drf-nested-routers>=0.94.0,<1.0", "django-filter>=25.0,<26.0", "django-ratelimit>=4.1.0,<5.0.0", "drf-spectacular>=0.28.0,<1.0", "drf-spectacular-sidecar>=2025.8.0,<2026.0", "django-json-widget>=2.0.0,<3.0", "django-import-export>=4.3.0,<5.0", "django-extensions>=4.1.0,<5.0", "django-constance>=4.3.0,<5.0", "django-unfold>=0.64.0,<1.0", "django-redis>=6.0.0,<7.0", "redis>=6.4.0,<7.0", "hiredis>=2.0.0,<4.0", "rearq>=0.2.0,<1.0", "setuptools>=75.0.0; python_version>='3.13'", "pyTelegramBotAPI>=4.28.0,<5.0", "coolname>=2.2.0,<3.0", "django-admin-rangefilter>=0.13.0,<1.0", "python-json-logger>=3.3.0,<4.0", "requests>=2.32.0,<3.0", "tiktoken>=0.11.0,<1.0", "openai>=1.107.0,<2.0", "twilio>=9.8.0,<10.0", "sendgrid>=6.12.0,<7.0", "beautifulsoup4>=4.13.0,<5.0", "lxml>=6.0.0,<7.0", "pgvector>=0.4.0,<1.0", "tenacity>=9.1.2,<10.0.0", "mypy (>=1.18.2,<2.0.0)", "django-tailwind[reload] (>=4.2.0,<5.0.0)", "jinja2 (>=3.1.6,<4.0.0)", "django-axes[ipware] (>=8.0.0,<9.0.0)", "pydantic-settings (>=2.11.0,<3.0.0)", "pytz>=2025.1", "httpx>=0.28.1,<1.0", "mistune>=3.1.4,<4.0",]
14
14
  [[project.authors]]
15
15
  name = "Django-CFG Team"
16
16
  email = "info@djangocfg.com"
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.4.108
3
+ Version: 1.4.110
4
4
  Summary: Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features.
5
5
  Project-URL: Homepage, https://djangocfg.com
6
6
  Project-URL: Documentation, https://djangocfg.com
@@ -58,6 +58,7 @@ Requires-Dist: httpx<1.0,>=0.28.1
58
58
  Requires-Dist: jinja2<4.0.0,>=3.1.6
59
59
  Requires-Dist: loguru<1.0,>=0.7.0
60
60
  Requires-Dist: lxml<7.0,>=6.0.0
61
+ Requires-Dist: mistune<4.0,>=3.1.4
61
62
  Requires-Dist: mypy<2.0.0,>=1.18.2
62
63
  Requires-Dist: ngrok>=1.5.1; python_version >= '3.12'
63
64
  Requires-Dist: openai<2.0,>=1.107.0
@@ -1,5 +1,5 @@
1
1
  django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- django_cfg/__init__.py,sha256=IOva6NUiepfrbs-A0jIjuXF_Ukn76RAB97unffae6Tw,1621
2
+ django_cfg/__init__.py,sha256=HbtRDtHlqf7LLLnJjYEq_tMe99Y4cjV_Kx8txhAOD_k,1621
3
3
  django_cfg/apps.py,sha256=72m3uuvyqGiLx6gOfE-BD3P61jddCCERuBOYpxTX518,1605
4
4
  django_cfg/config.py,sha256=y4Z3rnYsHBE0TehpwAIPaxr---mkvyKrZGGsNwYso74,1398
5
5
  django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
@@ -668,18 +668,16 @@ django_cfg/models/tasks/config.py,sha256=0Tc7O2mVQLc8IYXMm__cdeP0ytYn_NucL2xyA95
668
668
  django_cfg/models/tasks/utils.py,sha256=9TEbdxgd0N_O2T_7GGwkyPeDg4H7tSKLHoHG_n8edqg,4459
669
669
  django_cfg/modules/__init__.py,sha256=Ip9WMpzImEwIAywpFwU056_v0O9oIGG7nCT1YSArxkw,316
670
670
  django_cfg/modules/base.py,sha256=Grmgxc5dvnAEM1sudWEWO4kv8L0Ks-y32nxTk2vwdjQ,6272
671
- django_cfg/modules/django_admin/CHANGELOG_CODE_METHODS.md,sha256=HfE_rUlovx1zX_1hkzQsjwghaFvIvUWjZ_Aume8lhIs,3823
672
- django_cfg/modules/django_admin/IMPORT_EXPORT_FIX.md,sha256=F8z4oHZk7jkRChxW8tKsVf0Q_OujjlBUs8UJNirn55Y,2055
673
- django_cfg/modules/django_admin/RESOURCE_CONFIG_ENHANCEMENT.md,sha256=MKUpRHMqM3KDEKZthWhul-4EoSnE75kkWd7IcEzwYDQ,8729
674
- django_cfg/modules/django_admin/__init__.py,sha256=LYVFCVgN6Irr80dK8SkXgKQ-Xgg2MyRL5CgfOgmSt8A,3190
671
+ django_cfg/modules/django_admin/__init__.py,sha256=ncTMbxR7ccVd5xS-sZE_yJODjVMFq8HDFaDnP6lyvIg,3328
675
672
  django_cfg/modules/django_admin/base/__init__.py,sha256=tzre09bnD_SlS-pA30WzYZRxyvch7eLq3q0wLEcZOmc,118
676
- django_cfg/modules/django_admin/base/pydantic_admin.py,sha256=v2U-Tm_zWkDsh1CGmYL-xJdsVvHKX7MjUMrFwpY6wpw,20879
673
+ django_cfg/modules/django_admin/base/pydantic_admin.py,sha256=EZnkPFXDHvqR-JtDfYvJfg5_Nocn7svgXvQdVsTbv9A,24389
677
674
  django_cfg/modules/django_admin/base/unfold_admin.py,sha256=iqpRWSkzW5HktXDuuG7G3J6RoIfW48dWPMJTa7Yk08g,729
678
- django_cfg/modules/django_admin/config/__init__.py,sha256=IjD2pJBb_rboj9V4_4uzBmzIIzm4a5Xy8XIieL5myww,755
675
+ django_cfg/modules/django_admin/config/__init__.py,sha256=HDuJxhAS27EiL1pXLNvMePREB2gfoS2i_0Ur1ITCjAM,926
679
676
  django_cfg/modules/django_admin/config/action_config.py,sha256=JjS01JxLT-FzUVq7RlKaB7L38wmVL8uibXO_iXZcljo,1668
680
- django_cfg/modules/django_admin/config/admin_config.py,sha256=7ozVnLqJcUrHu-UK-PdOfuX2UOvR1gkigThgQsfO0wY,5030
677
+ django_cfg/modules/django_admin/config/admin_config.py,sha256=TnPI-kLZj6uoIH7opCD70HQ6I_yJ7jPKnphS0h9P42s,5242
681
678
  django_cfg/modules/django_admin/config/background_task_config.py,sha256=7-8B1rhpeafanVxtjFQUx0mVjcA5xmxZIxqKzaBwMX0,1760
682
- django_cfg/modules/django_admin/config/field_config.py,sha256=TXdmz1GHC0N7euzqgB9p5EhFCYzsEobb8VSgS4kG4ho,8928
679
+ django_cfg/modules/django_admin/config/documentation_config.py,sha256=lI_gMSWCtyKmdyttLNdgbg_zbGgrwXA-QoLxVOXJj9A,14189
680
+ django_cfg/modules/django_admin/config/field_config.py,sha256=LeHoumm-lmiYIu0dBmoyCdTkddQdcv20_KrD3WRhkQ0,11743
683
681
  django_cfg/modules/django_admin/config/fieldset_config.py,sha256=5BPUWO_HvS6YhPU_vqQPzRT2y3OIPrBCioFuer5-mrA,1249
684
682
  django_cfg/modules/django_admin/config/resource_config.py,sha256=6ELWSuHlrpNqV2wO2sL_o2mE2DXLd_solcsdN5O6eQg,3962
685
683
  django_cfg/modules/django_admin/icons/README.md,sha256=j-NUixSC1QJh7PqYKxLZpPrTxKrAnx0urQraXgv4JHI,5612
@@ -691,12 +689,17 @@ django_cfg/modules/django_admin/models/action_models.py,sha256=clzu_DJOexrqyKfkv
691
689
  django_cfg/modules/django_admin/models/badge_models.py,sha256=ob5NOtx8YZ9KRVmn9fxLWg_OXbvt3fLhhk8wwU5eP8E,538
692
690
  django_cfg/modules/django_admin/models/base.py,sha256=fmgS-X3GAGEBXiBy4OQGeOMzB7qIkvzDXla5-_EHAQE,611
693
691
  django_cfg/modules/django_admin/models/display_models.py,sha256=rONmja60Qz8n4qNvzjXIQryO_-UGZK136SrElO8iFfM,1087
692
+ django_cfg/modules/django_admin/templates/django_admin/change_form_docs.html,sha256=MtP-UKebcOdz6YsZxLV5-UFq6WfmlV3mEF7qsCdYOKg,815
693
+ django_cfg/modules/django_admin/templates/django_admin/change_list_docs.html,sha256=MfH6mf9v6E3y97TjvjvZ2A7RMhOkKd6CtxEw3tPV5WE,802
694
+ django_cfg/modules/django_admin/templates/django_admin/documentation_block.html,sha256=IHB1dDGavvHNnez6modI1wJxvoXuZSQmsBu0nheYcZA,13974
695
+ django_cfg/modules/django_admin/templates/django_admin/markdown_docs_block.html,sha256=nO9oEyDYozYF-euaLzE0Cn_Yo_LGUlta-BsRgShDG10,1992
694
696
  django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md,sha256=rp5qMG-Ci30fIs6EyZctjEbhQwxfNq9e36B4CTZOnR0,9456
695
- django_cfg/modules/django_admin/utils/__init__.py,sha256=DkMCGBbSe6Xfgdse-C8JkcqTRxtq3k2tMKxt39F1GfA,649
697
+ django_cfg/modules/django_admin/utils/__init__.py,sha256=5V1Og6fpIpRqDIoqF7jYnBmPl9TjV5kByghzT6iOpPs,745
696
698
  django_cfg/modules/django_admin/utils/badges.py,sha256=eZ1UThdwvv2cHAIDc4vTrD5xAet7fmeb9h9yj4ZXJ-c,6328
697
699
  django_cfg/modules/django_admin/utils/decorators.py,sha256=s4jTcgPklY4T4xjXsMHpShd71K_LzgKogem9JKBgNzk,8371
698
700
  django_cfg/modules/django_admin/utils/displays.py,sha256=f-FT1mD-X56X6xLDJ9FuCi4oz_UYirdLosYrXnJP3hI,7862
699
- django_cfg/modules/django_admin/utils/html_builder.py,sha256=d-_laj5Yz7gY0Xkwg1vNIlcK-N8F1Z3atM4-t1584pw,9793
701
+ django_cfg/modules/django_admin/utils/html_builder.py,sha256=EMzz1rGJCM7jTgfWb8LKqSdok1oq_oaWbBk6v9qvCrU,12831
702
+ django_cfg/modules/django_admin/utils/markdown_renderer.py,sha256=nrMopdVA114W_UOvYFUtnbfj_Di0LHmtmrxG53hsn_Y,13834
700
703
  django_cfg/modules/django_admin/widgets/__init__.py,sha256=mmPw5FMYR21GDGFMr-MOCcdM4G2_ZR60ClInHjdnTBE,115
701
704
  django_cfg/modules/django_admin/widgets/registry.py,sha256=q0Yyaze5ZTYLJslPyX9e4Few_FGLnGBQwtNln9Okyt4,5610
702
705
  django_cfg/modules/django_client/__init__.py,sha256=iHaGKbsyR2wMmVCWNsETC7cwB60fZudvnFMiK1bchW8,529
@@ -845,16 +848,6 @@ django_cfg/modules/django_client/spectacular/__init__.py,sha256=M8fG-odu2ltkG36a
845
848
  django_cfg/modules/django_client/spectacular/async_detection.py,sha256=S_pwGR7_2SIWHjZJyiu7SCfySF3Nr3P8eqjDyBSkkLs,5731
846
849
  django_cfg/modules/django_client/spectacular/enum_naming.py,sha256=FMJyJiS7nBs-Q9yDs5M0qsNLVX8UJhFgJK9Svo-T2tk,6486
847
850
  django_cfg/modules/django_client/spectacular/schema.py,sha256=6oJxoNsSsZLlM2qmKuJGU1sPPyEGr1Xit_xBfLvprHU,1639
848
- django_cfg/modules/django_client/system/__init__.py,sha256=uDARdYmmJ2yR0pCNaIQpsAPcErvhO7FNYX0CTbow8Y8,622
849
- django_cfg/modules/django_client/system/base_generator.py,sha256=skEf8_vXz3AeHZqOg_BjqsL1AToPn-RQY0211-ZXncM,4396
850
- django_cfg/modules/django_client/system/generate_mjs_clients.py,sha256=N8GsRUgrSJ4MaiJ99q5tvYg3K7OStF6VjjZO5J-7woQ,5749
851
- django_cfg/modules/django_client/system/mjs_generator.py,sha256=c6ktFR8Ipuju3CGPe6NUnlBwiqPrT_ik1ysRsBcd9Nw,7351
852
- django_cfg/modules/django_client/system/schema_parser.py,sha256=0IzTKcSEWDDe4FkAqNOSWG4MQTbQzg-bAXHOV0v3sZs,7216
853
- django_cfg/modules/django_client/system/templates/api_client.js.j2,sha256=u3CLl4LVNgGrusuhiY8Q7btWpwkF2ijYxtZmFCsbBfk,2803
854
- django_cfg/modules/django_client/system/templates/app_index.js.j2,sha256=PPxiIaYw7U5VeMv5bySu3srxFHRBwo5mt1yL-l1SZ_0,387
855
- django_cfg/modules/django_client/system/templates/base_client.js.j2,sha256=Qjngf8783y47FnQ0ZOvtdFTSF6t-vYqvJaFPJmaTY40,4524
856
- django_cfg/modules/django_client/system/templates/main_index.js.j2,sha256=rv5uYR4hjZRRCjWGqU_a7UsDbPx-CMu8itKdVBNsLHo,1827
857
- django_cfg/modules/django_client/system/templates/types.js.j2,sha256=hO6y0WwMOpkd3sMfmu9b2X7I2dyosodI8oW9Y8evhZo,744
858
851
  django_cfg/modules/django_currency/README.md,sha256=4R7KtF-rhR6yRg5jbsXwOVXMZNGEpOyW4N3WGN89W7A,4670
859
852
  django_cfg/modules/django_currency/__init__.py,sha256=Vm9-KPM7HqK0SM35YHjh2ciJoTYkDz3PR2bod6B2Alg,2490
860
853
  django_cfg/modules/django_currency/clients/__init__.py,sha256=bgrd3kJIgY-D80AQfs45pq9QanOkHdLfz2yAKsRnCyY,235
@@ -1025,7 +1018,7 @@ django_cfg/static/admin/js/alpine/commands-section.js,sha256=8z2MQNwZF9Tx_2EK1AY
1025
1018
  django_cfg/static/admin/js/alpine/dashboard-tabs.js,sha256=ob8Q_I9lFLDv_hFERXgTyvqMDBspAGfzCxI_7slRur4,1354
1026
1019
  django_cfg/static/admin/js/alpine/system-metrics.js,sha256=m-Fg55K_vpHXToD46PXL9twl4OBF_V9MONvbSWbQqDw,440
1027
1020
  django_cfg/static/admin/js/alpine/toggle-section.js,sha256=T141NFmy0fRJyGGuuaCJRjJXwPam-xxtQNW1hi8BJbc,672
1028
- django_cfg/static/frontend/admin.zip,sha256=pLsFyDXNjsrLStXYZBYw820gPayTk8fYGZAy8_6kVtM,7645824
1021
+ django_cfg/static/frontend/admin.zip,sha256=MMRlhWT3NGk3_OkD06pRpNI9dskRQshbGdKAeUQdLFQ,7645836
1029
1022
  django_cfg/static/js/api-loader.mjs,sha256=boGqqRGnFR-Mzo_RQOjhAzNvsb7QxZddSwMKROzkk9Q,5163
1030
1023
  django_cfg/static/js/api/base.mjs,sha256=KUxZHHdELAV8mNnACpwJRvaQhdJxp-n5LFEQ4oUZxBo,4707
1031
1024
  django_cfg/static/js/api/index.mjs,sha256=_-Q04jjHcgwi4CGfiaLyiOR6NW7Yu1HBhJWp2J1cjpc,2538
@@ -1060,9 +1053,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
1060
1053
  django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
1061
1054
  django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
1062
1055
  django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1063
- django_cfg/pyproject.toml,sha256=Ut3Yvuw83bf3FmZzYAJL24fp1zbblv_c2p61XF7318A,8614
1064
- django_cfg-1.4.108.dist-info/METADATA,sha256=IOJxCR_vzoWD4SuFisznEMmiEkvpx-mg_ktcf-TB81U,23801
1065
- django_cfg-1.4.108.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1066
- django_cfg-1.4.108.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1067
- django_cfg-1.4.108.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1068
- django_cfg-1.4.108.dist-info/RECORD,,
1056
+ django_cfg/pyproject.toml,sha256=lz106jlJ2YsUxr9D0NqVgp5x2TZSNT1vbTrZ_YaeydQ,8637
1057
+ django_cfg-1.4.110.dist-info/METADATA,sha256=xG_TAByn-K2qm3HlL_4cMNNHz4UpMrpV5hVIzTNGdKU,23836
1058
+ django_cfg-1.4.110.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1059
+ django_cfg-1.4.110.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1060
+ django_cfg-1.4.110.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1061
+ django_cfg-1.4.110.dist-info/RECORD,,
@@ -1,153 +0,0 @@
1
- # CHANGELOG: Code Display Methods
2
-
3
- ## [2024-10-29] Added `code()` and `code_block()` methods to HtmlBuilder
4
-
5
- ### Summary
6
- Added two new methods to `HtmlBuilder` for displaying code in Django Admin interfaces with proper styling, scrolling support, and color variants.
7
-
8
- ### New Methods
9
-
10
- #### 1. `html.code(text, css_class="")`
11
- Display inline code snippets.
12
-
13
- **Use cases:**
14
- - File paths
15
- - Short commands
16
- - Configuration keys
17
- - Single-line code
18
-
19
- **Example:**
20
- ```python
21
- self.html.code("/path/to/file")
22
- self.html.code("DEBUG=True")
23
- ```
24
-
25
- #### 2. `html.code_block(text, language=None, max_height=None, variant="default")`
26
- Display multi-line code blocks with:
27
- - Optional scrolling (via `max_height`)
28
- - Color variants (default, warning, danger, success, info)
29
- - Language hints for future syntax highlighting
30
- - Proper text wrapping and escaping
31
-
32
- **Use cases:**
33
- - JSON configuration
34
- - Command output (stdout/stderr)
35
- - Error messages and stack traces
36
- - Log files
37
- - API requests/responses
38
- - SQL queries
39
-
40
- **Examples:**
41
- ```python
42
- # JSON
43
- self.html.code_block(json.dumps(data, indent=2), language="json")
44
-
45
- # Logs with scrolling
46
- self.html.code_block(obj.stdout, max_height="400px")
47
-
48
- # Errors
49
- self.html.code_block(obj.error_message, variant="danger", max_height="300px")
50
-
51
- # Warnings
52
- self.html.code_block(obj.stderr, variant="warning", max_height="400px")
53
- ```
54
-
55
- ### Files Modified
56
-
57
- **`utils/html_builder.py`:**
58
- - Added `code()` method (lines 207-227)
59
- - Added `code_block()` method (lines 229-278)
60
-
61
- ### Features
62
-
63
- ✅ **Automatic HTML Escaping** - Prevents XSS
64
- ✅ **Dark Mode Support** - Full Tailwind dark: classes
65
- ✅ **Responsive Design** - Works on mobile
66
- ✅ **Color Variants** - 5 variants for different contexts
67
- ✅ **Scrollable Content** - Max height with overflow
68
- ✅ **Consistent Styling** - Matches django-cfg design system
69
- ✅ **Future-proof** - Language parameter for syntax highlighting
70
- ✅ **Type Hints** - Full typing support
71
-
72
- ### Technical Details
73
-
74
- **Dependencies:**
75
- - Django's `format_html` and `escape`
76
- - Tailwind CSS classes (already in use)
77
- - No additional packages required
78
-
79
- **Styling:**
80
- - Uses existing Tailwind utility classes
81
- - Follows django-cfg color system
82
- - Light/dark mode variants
83
-
84
- **Performance:**
85
- - No JavaScript required
86
- - Pure CSS for scrolling
87
- - Minimal HTML overhead
88
-
89
- ### Breaking Changes
90
- None - purely additive changes.
91
-
92
- ### Backward Compatibility
93
- 100% backward compatible. All existing code continues to work.
94
-
95
- ### Migration Path
96
- Optional migration from manual HTML:
97
-
98
- **Before:**
99
- ```python
100
- def output_display(self, obj):
101
- return format_html('<pre>{}</pre>', obj.stdout)
102
- ```
103
-
104
- **After:**
105
- ```python
106
- def output_display(self, obj):
107
- return self.html.code_block(obj.stdout, max_height="400px")
108
- ```
109
-
110
- ### Real-World Usage
111
-
112
- Already implemented in:
113
- - `apps.adminpanel.admin.config.CommandExecutionAdmin`
114
- - JSON args/options display
115
- - stdout/stderr output with scrolling
116
- - Error messages with danger variant
117
-
118
- ### Documentation
119
-
120
- Full documentation available in:
121
- - `CODE_BLOCK_DOCS.md` - Complete guide with examples
122
- - API Reference (to be updated)
123
- - Quick Start guide (to be updated)
124
-
125
- ### Testing
126
-
127
- Manual testing performed:
128
- - ✅ Inline code rendering
129
- - ✅ Code block with/without scrolling
130
- - ✅ All 5 color variants
131
- - ✅ JSON formatting
132
- - ✅ Long text with wrapping
133
- - ✅ Dark mode
134
- - ✅ HTML escaping
135
-
136
- ### Next Steps
137
-
138
- Recommended for future:
139
- 1. Add to API Reference documentation
140
- 2. Add to Examples documentation
141
- 3. Consider syntax highlighting library integration (Prism.js, highlight.js)
142
- 4. Add to django-cfg changelog
143
- 5. Update tutorial/quick-start docs
144
-
145
- ### Contributors
146
- - Implementation: Claude Code
147
- - Use Case: CommandExecution admin in StockAPIS project
148
-
149
- ---
150
-
151
- **Version:** Added in django-cfg dev build (2024-10-29)
152
- **Status:** ✅ Complete and tested
153
- **Impact:** Low - purely additive feature