django-cfg 1.4.75__py3-none-any.whl → 1.4.77__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/agents/__init__.py +1 -1
- django_cfg/apps/agents/integration/registry.py +1 -1
- django_cfg/apps/agents/patterns/content_agents.py +1 -1
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +1 -1
- django_cfg/apps/centrifugo/views/dashboard.py +13 -0
- django_cfg/apps/centrifugo/views/testing_api.py +74 -15
- django_cfg/apps/tasks/views/dashboard.py +4 -4
- django_cfg/core/generation/integration_generators/api.py +5 -2
- django_cfg/management/commands/check_endpoints.py +1 -1
- django_cfg/modules/django_client/core/generator/python/models_generator.py +6 -6
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +0 -2
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +0 -1
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -1
- django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +7 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +2 -2
- django_cfg/modules/django_unfold/callbacks/main.py +27 -25
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/admin/css/constance.css +44 -0
- django_cfg/static/admin/css/dashboard.css +6 -170
- django_cfg/static/admin/css/layout.css +21 -0
- django_cfg/static/admin/css/tabs.css +95 -0
- django_cfg/static/admin/css/theme.css +74 -0
- django_cfg/static/admin/js/alpine/activity-tracker.js +55 -0
- django_cfg/static/admin/js/alpine/chart.js +101 -0
- django_cfg/static/admin/js/alpine/command-modal.js +159 -0
- django_cfg/static/admin/js/alpine/commands-panel.js +139 -0
- django_cfg/static/admin/js/alpine/commands-section.js +260 -0
- django_cfg/static/admin/js/alpine/dashboard-tabs.js +46 -0
- django_cfg/static/admin/js/alpine/system-metrics.js +20 -0
- django_cfg/static/admin/js/alpine/toggle-section.js +28 -0
- django_cfg/static/admin/js/utils.js +60 -0
- django_cfg/templates/admin/components/modal.html +1 -1
- django_cfg/templates/admin/constance/change_list.html +3 -42
- django_cfg/templates/admin/index.html +0 -8
- django_cfg/templates/admin/layouts/base_dashboard.html +4 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +104 -502
- django_cfg/templates/admin/sections/commands_section.html +374 -451
- django_cfg/templates/admin/sections/documentation_section.html +13 -33
- django_cfg/templates/admin/snippets/components/activity_tracker.html +27 -49
- django_cfg/templates/admin/snippets/components/charts_section.html +8 -74
- django_cfg/templates/admin/snippets/components/django_commands.html +94 -181
- django_cfg/templates/admin/snippets/components/system_metrics.html +18 -10
- django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +2 -2
- django_cfg/templates/admin/snippets/tabs/commands_tab.html +48 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +1 -190
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +1 -1
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.dist-info}/METADATA +1 -1
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.dist-info}/RECORD +52 -44
- django_cfg/static/admin/js/commands.js +0 -171
- django_cfg/static/admin/js/dashboard.js +0 -126
- django_cfg/templates/admin/components/management_commands.js +0 -375
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +0 -322
- django_cfg/templates/admin/snippets/components/recent_activity.html +0 -35
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -32,7 +32,7 @@ __all__ = [
|
|
|
32
32
|
def __getattr__(name):
|
|
33
33
|
"""Lazy import for agents components."""
|
|
34
34
|
if name == "DjangoAgent":
|
|
35
|
-
from .core.
|
|
35
|
+
from .core.django_agent import DjangoAgent
|
|
36
36
|
return DjangoAgent
|
|
37
37
|
elif name == "SimpleOrchestrator":
|
|
38
38
|
from .core.orchestrator import SimpleOrchestrator
|
|
@@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Type
|
|
|
7
7
|
|
|
8
8
|
from django.contrib.auth.models import User
|
|
9
9
|
|
|
10
|
-
from ..core.
|
|
10
|
+
from ..core.django_agent import DjangoAgent
|
|
11
11
|
from ..core.dependencies import DjangoDeps
|
|
12
12
|
from ..core.orchestrator import SimpleOrchestrator
|
|
13
13
|
from ..models.registry import AgentDefinition
|
|
@@ -6,7 +6,7 @@ from typing import Any, Dict, List
|
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
|
-
from ..core.
|
|
9
|
+
from ..core.django_agent import DjangoAgent
|
|
10
10
|
from ..core.dependencies import ContentDeps, RunContext
|
|
11
11
|
from ..core.models import ValidationResult
|
|
12
12
|
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
parseInt(document.getElementById('pub-ack-timeout').value)
|
|
290
290
|
).then(result => {
|
|
291
291
|
if (result.success) {
|
|
292
|
-
alert('Message published successfully
|
|
292
|
+
alert('Message published successfully!\nMessage ID: ' + result.message_id + '\nDelivered: ' + result.delivered + '\nACKs: ' + result.acks_received);
|
|
293
293
|
} else {
|
|
294
294
|
alert('Failed to publish message: ' + result.error);
|
|
295
295
|
}
|
|
@@ -4,12 +4,25 @@ Dashboard view for Centrifugo monitoring.
|
|
|
4
4
|
|
|
5
5
|
from django.contrib.admin.views.decorators import staff_member_required
|
|
6
6
|
from django.shortcuts import render
|
|
7
|
+
from django.urls import reverse
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
@staff_member_required
|
|
10
11
|
def dashboard_view(request):
|
|
11
12
|
"""Render the Centrifugo dashboard template."""
|
|
13
|
+
|
|
14
|
+
# Navigation items for the navbar
|
|
15
|
+
nav_items = [
|
|
16
|
+
{
|
|
17
|
+
'label': 'Logs',
|
|
18
|
+
'url': reverse('admin:django_cfg_centrifugo_centrifugolog_changelist'),
|
|
19
|
+
'icon': 'list_alt',
|
|
20
|
+
'active': False,
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
|
|
12
24
|
context = {
|
|
13
25
|
'page_title': 'Centrifugo Monitor Dashboard',
|
|
26
|
+
'centrifugo_nav_items': nav_items,
|
|
14
27
|
}
|
|
15
28
|
return render(request, 'django_cfg_centrifugo/pages/dashboard.html', context)
|
|
@@ -119,10 +119,11 @@ class CentrifugoTestingAPIViewSet(viewsets.ViewSet):
|
|
|
119
119
|
raise ValueError("Centrifugo not configured")
|
|
120
120
|
|
|
121
121
|
headers = {"Content-Type": "application/json"}
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
# Use centrifugo_api_key for direct Centrifugo API calls
|
|
123
|
+
if config.centrifugo_api_key:
|
|
124
|
+
headers["X-API-Key"] = config.centrifugo_api_key
|
|
124
125
|
|
|
125
|
-
# Use wrapper URL as base
|
|
126
|
+
# Use wrapper URL as base (which points to Centrifugo in local setup)
|
|
126
127
|
base_url = config.wrapper_url.rstrip("/")
|
|
127
128
|
|
|
128
129
|
self._http_client = httpx.AsyncClient(
|
|
@@ -300,7 +301,7 @@ class CentrifugoTestingAPIViewSet(viewsets.ViewSet):
|
|
|
300
301
|
self, channel: str, data: Dict[str, Any], wait_for_ack: bool, ack_timeout: int
|
|
301
302
|
) -> Dict[str, Any]:
|
|
302
303
|
"""
|
|
303
|
-
Publish message to
|
|
304
|
+
Publish message to Centrifugo API.
|
|
304
305
|
|
|
305
306
|
Args:
|
|
306
307
|
channel: Target channel
|
|
@@ -309,20 +310,78 @@ class CentrifugoTestingAPIViewSet(viewsets.ViewSet):
|
|
|
309
310
|
ack_timeout: ACK timeout in seconds
|
|
310
311
|
|
|
311
312
|
Returns:
|
|
312
|
-
|
|
313
|
+
Centrifugo API response formatted for wrapper compatibility
|
|
313
314
|
"""
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
315
|
+
import uuid
|
|
316
|
+
import time
|
|
317
|
+
from ..services.logging import CentrifugoLogger
|
|
318
|
+
|
|
319
|
+
# Generate unique message ID
|
|
320
|
+
message_id = str(uuid.uuid4())
|
|
321
|
+
start_time = time.time()
|
|
322
|
+
|
|
323
|
+
# Create log entry
|
|
324
|
+
log_entry = await CentrifugoLogger.create_log_async(
|
|
325
|
+
message_id=message_id,
|
|
326
|
+
channel=channel,
|
|
327
|
+
data=data,
|
|
328
|
+
wait_for_ack=wait_for_ack,
|
|
329
|
+
ack_timeout=ack_timeout if wait_for_ack else None,
|
|
330
|
+
is_notification=True,
|
|
331
|
+
user=self.request.user if self.request and self.request.user.is_authenticated else None,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
# Centrifugo API format: POST /api with method in body
|
|
336
|
+
payload = {
|
|
337
|
+
"method": "publish",
|
|
338
|
+
"params": {
|
|
339
|
+
"channel": channel,
|
|
340
|
+
"data": data,
|
|
341
|
+
}
|
|
342
|
+
}
|
|
319
343
|
|
|
320
|
-
|
|
321
|
-
|
|
344
|
+
response = await self.http_client.post("/api", json=payload)
|
|
345
|
+
response.raise_for_status()
|
|
346
|
+
result = response.json()
|
|
322
347
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
348
|
+
# Calculate duration
|
|
349
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
350
|
+
|
|
351
|
+
# Mark as success
|
|
352
|
+
if log_entry:
|
|
353
|
+
await CentrifugoLogger.mark_success_async(
|
|
354
|
+
log_entry,
|
|
355
|
+
acks_received=0,
|
|
356
|
+
duration_ms=duration_ms,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Transform Centrifugo response to match wrapper API format
|
|
360
|
+
return {
|
|
361
|
+
"published": True,
|
|
362
|
+
"message_id": message_id,
|
|
363
|
+
"channel": channel,
|
|
364
|
+
"acks_received": 0,
|
|
365
|
+
"delivered": True,
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
# Calculate duration
|
|
370
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
371
|
+
|
|
372
|
+
# Mark as failed
|
|
373
|
+
if log_entry:
|
|
374
|
+
from asgiref.sync import sync_to_async
|
|
375
|
+
from ..models import CentrifugoLog
|
|
376
|
+
|
|
377
|
+
await sync_to_async(CentrifugoLog.objects.mark_failed)(
|
|
378
|
+
log_instance=log_entry,
|
|
379
|
+
error_code=type(e).__name__,
|
|
380
|
+
error_message=str(e),
|
|
381
|
+
duration_ms=duration_ms,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
raise
|
|
326
385
|
|
|
327
386
|
async def _send_ack_to_wrapper(
|
|
328
387
|
self, message_id: str, client_id: str
|
|
@@ -33,12 +33,12 @@ def dashboard_view(request):
|
|
|
33
33
|
{
|
|
34
34
|
'label': 'Task History',
|
|
35
35
|
'url': '/admin/django_dramatiq/task/',
|
|
36
|
-
'icon': '
|
|
36
|
+
'icon': 'history',
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
'label': 'Settings',
|
|
40
40
|
'url': '/admin/constance/config/',
|
|
41
|
-
'icon': '
|
|
41
|
+
'icon': 'settings',
|
|
42
42
|
},
|
|
43
43
|
]
|
|
44
44
|
|
|
@@ -59,12 +59,12 @@ def dashboard_view(request):
|
|
|
59
59
|
{
|
|
60
60
|
'label': 'Task History',
|
|
61
61
|
'url': '/admin/django_dramatiq/task/',
|
|
62
|
-
'icon': '
|
|
62
|
+
'icon': 'history',
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
'label': 'Settings',
|
|
66
66
|
'url': '/admin/constance/config/',
|
|
67
|
-
'icon': '
|
|
67
|
+
'icon': 'settings',
|
|
68
68
|
},
|
|
69
69
|
]
|
|
70
70
|
|
|
@@ -220,9 +220,12 @@ class APIFrameworksGenerator:
|
|
|
220
220
|
Dictionary with Spectacular settings
|
|
221
221
|
"""
|
|
222
222
|
# Import authentication extension to register it with drf-spectacular
|
|
223
|
+
# Only import if apps are ready to avoid warnings
|
|
223
224
|
try:
|
|
224
|
-
from
|
|
225
|
-
|
|
225
|
+
from django.apps import apps
|
|
226
|
+
if apps.ready:
|
|
227
|
+
from django_cfg.middleware import authentication # noqa: F401
|
|
228
|
+
except (ImportError, RuntimeError):
|
|
226
229
|
pass
|
|
227
230
|
|
|
228
231
|
# Check if Spectacular settings exist (from OpenAPI Client or elsewhere)
|
|
@@ -13,7 +13,7 @@ import json
|
|
|
13
13
|
from django.core.management.base import BaseCommand
|
|
14
14
|
from django.urls import reverse
|
|
15
15
|
|
|
16
|
-
from django_cfg.apps.api.endpoints.checker import check_all_endpoints
|
|
16
|
+
from django_cfg.apps.api.endpoints.endpoints_status.checker import check_all_endpoints
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Command(BaseCommand):
|
|
@@ -267,11 +267,11 @@ class ModelsGenerator:
|
|
|
267
267
|
if schema.properties:
|
|
268
268
|
for prop in schema.properties.values():
|
|
269
269
|
if prop.enum and prop.name:
|
|
270
|
-
enum_names.add(
|
|
270
|
+
enum_names.add(self.base.sanitize_enum_name(prop.name))
|
|
271
271
|
elif prop.ref and prop.ref in self.context.schemas:
|
|
272
272
|
ref_schema = self.context.schemas[prop.ref]
|
|
273
273
|
if ref_schema.enum:
|
|
274
|
-
enum_names.add(
|
|
274
|
+
enum_names.add(self.base.sanitize_enum_name(prop.ref))
|
|
275
275
|
|
|
276
276
|
# Request models
|
|
277
277
|
for name, schema in schemas.items():
|
|
@@ -282,11 +282,11 @@ class ModelsGenerator:
|
|
|
282
282
|
if schema.properties:
|
|
283
283
|
for prop in schema.properties.values():
|
|
284
284
|
if prop.enum and prop.name:
|
|
285
|
-
enum_names.add(
|
|
285
|
+
enum_names.add(self.base.sanitize_enum_name(prop.name))
|
|
286
286
|
elif prop.ref and prop.ref in self.context.schemas:
|
|
287
287
|
ref_schema = self.context.schemas[prop.ref]
|
|
288
288
|
if ref_schema.enum:
|
|
289
|
-
enum_names.add(
|
|
289
|
+
enum_names.add(self.base.sanitize_enum_name(prop.ref))
|
|
290
290
|
|
|
291
291
|
# Patch models
|
|
292
292
|
for name, schema in schemas.items():
|
|
@@ -297,11 +297,11 @@ class ModelsGenerator:
|
|
|
297
297
|
if schema.properties:
|
|
298
298
|
for prop in schema.properties.values():
|
|
299
299
|
if prop.enum and prop.name:
|
|
300
|
-
enum_names.add(
|
|
300
|
+
enum_names.add(self.base.sanitize_enum_name(prop.name))
|
|
301
301
|
elif prop.ref and prop.ref in self.context.schemas:
|
|
302
302
|
ref_schema = self.context.schemas[prop.ref]
|
|
303
303
|
if ref_schema.enum:
|
|
304
|
-
enum_names.add(
|
|
304
|
+
enum_names.add(self.base.sanitize_enum_name(prop.ref))
|
|
305
305
|
|
|
306
306
|
template = self.jinja_env.get_template('models/app_models.py.jinja')
|
|
307
307
|
content = template.render(
|
django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja
CHANGED
|
@@ -36,13 +36,11 @@ class APIClient:
|
|
|
36
36
|
self._client = RetryAsyncClient(
|
|
37
37
|
base_url=self.base_url,
|
|
38
38
|
retry_config=retry_config,
|
|
39
|
-
timeout=30.0,
|
|
40
39
|
**kwargs,
|
|
41
40
|
)
|
|
42
41
|
else:
|
|
43
42
|
self._client = httpx.AsyncClient(
|
|
44
43
|
base_url=self.base_url,
|
|
45
|
-
timeout=30.0,
|
|
46
44
|
**kwargs,
|
|
47
45
|
)
|
|
48
46
|
|
|
@@ -38,7 +38,8 @@ from .{{ tag.slug }} import {{ tag.class_name }}
|
|
|
38
38
|
{% endfor %}
|
|
39
39
|
{% if has_enums %}
|
|
40
40
|
from . import enums
|
|
41
|
-
|
|
41
|
+
# NOTE: Individual enum imports commented out due to invalid Python syntax with dotted names
|
|
42
|
+
# from .enums import {{ enum_names | join(', ') }}
|
|
42
43
|
{% endif %}
|
|
43
44
|
|
|
44
45
|
TOKEN_KEY = "auth_token"
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
from enum import IntEnum,
|
|
1
|
+
from enum import IntEnum, Enum
|
|
2
|
+
|
|
3
|
+
# Python 3.10 compatibility: StrEnum was added in Python 3.11
|
|
4
|
+
# Use str + Enum instead for backward compatibility
|
|
5
|
+
class StrEnum(str, Enum):
|
|
6
|
+
"""String Enum for Python 3.10+ compatibility"""
|
|
7
|
+
pass
|
|
2
8
|
|
|
3
9
|
|
|
4
10
|
{% for enum in enums %}
|
|
@@ -67,13 +67,13 @@ Custom navbar (extending):
|
|
|
67
67
|
<nav class="hidden md:flex items-center space-x-1">
|
|
68
68
|
{% for item in nav_items %}
|
|
69
69
|
<a href="{{ item.url }}"
|
|
70
|
-
class="px-3 py-2 rounded-md text-sm font-medium transition-colors
|
|
70
|
+
class="px-3 py-2 rounded-md text-sm font-medium transition-colors flex items-center gap-1.5
|
|
71
71
|
{% if item.active %}
|
|
72
72
|
bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white
|
|
73
73
|
{% else %}
|
|
74
74
|
text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white
|
|
75
75
|
{% endif %}">
|
|
76
|
-
{% if item.icon %}<span class="
|
|
76
|
+
{% if item.icon %}<span class="material-icons text-sm">{{ item.icon }}</span>{% endif %}
|
|
77
77
|
{{ item.label }}
|
|
78
78
|
</a>
|
|
79
79
|
{% endfor %}
|
|
@@ -8,10 +8,11 @@ import json
|
|
|
8
8
|
import logging
|
|
9
9
|
from typing import Any, Dict
|
|
10
10
|
|
|
11
|
+
from django.template.loader import render_to_string
|
|
11
12
|
from django.utils import timezone
|
|
12
13
|
|
|
13
14
|
from django_cfg.core.state import get_current_config
|
|
14
|
-
from django_cfg.modules.django_dashboard.debug import
|
|
15
|
+
from django_cfg.modules.django_dashboard.debug import save_dashboard_render
|
|
15
16
|
from django_cfg.modules.django_dashboard.sections.commands import CommandsSection
|
|
16
17
|
from django_cfg.modules.django_dashboard.sections.documentation import DocumentationSection
|
|
17
18
|
|
|
@@ -113,48 +114,41 @@ class UnfoldCallbacks(
|
|
|
113
114
|
time_range=time_range,
|
|
114
115
|
navigation=navigation
|
|
115
116
|
)
|
|
116
|
-
# Debug: save render (only in debug mode)
|
|
117
|
-
if config and config.debug:
|
|
118
|
-
save_section_render('overview', overview_section)
|
|
119
117
|
except Exception as e:
|
|
120
118
|
logger.error(f"Failed to render overview section: {e}", exc_info=True)
|
|
121
119
|
overview_section = None
|
|
122
120
|
|
|
123
121
|
try:
|
|
124
122
|
stats_section = StatsSection(request).render()
|
|
125
|
-
# Debug: save render (only in debug mode)
|
|
126
|
-
if config and config.debug:
|
|
127
|
-
save_section_render('stats', stats_section)
|
|
128
123
|
except Exception as e:
|
|
129
124
|
logger.error(f"Failed to render stats section: {e}", exc_info=True)
|
|
130
125
|
stats_section = None
|
|
131
126
|
|
|
132
127
|
try:
|
|
133
128
|
system_section = SystemSection(request).render()
|
|
134
|
-
# Debug: save render (only in debug mode)
|
|
135
|
-
if config and config.debug:
|
|
136
|
-
save_section_render('system', system_section)
|
|
137
129
|
except Exception as e:
|
|
138
130
|
logger.error(f"Failed to render system section: {e}", exc_info=True)
|
|
139
131
|
system_section = None
|
|
140
132
|
|
|
141
133
|
try:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
# Generate documentation content first
|
|
135
|
+
doc_section = DocumentationSection(request)
|
|
136
|
+
doc_data = doc_section.get_data_old() # Get markdown/HTML content
|
|
137
|
+
documentation_content = doc_data.get('documentation_content') or doc_data.get('readme_content', '')
|
|
138
|
+
documentation_section = doc_section.render()
|
|
146
139
|
except Exception as e:
|
|
147
|
-
logger.error(f"Failed to render
|
|
148
|
-
|
|
140
|
+
logger.error(f"Failed to render documentation section: {e}", exc_info=True)
|
|
141
|
+
documentation_section = None
|
|
142
|
+
documentation_content = None
|
|
149
143
|
|
|
150
144
|
try:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
# Render commands section with documentation content
|
|
146
|
+
commands_section = CommandsSection(request).render(
|
|
147
|
+
documentation_content=documentation_content
|
|
148
|
+
)
|
|
155
149
|
except Exception as e:
|
|
156
|
-
logger.error(f"Failed to render
|
|
157
|
-
|
|
150
|
+
logger.error(f"Failed to render commands section: {e}", exc_info=True)
|
|
151
|
+
commands_section = None
|
|
158
152
|
|
|
159
153
|
# Extract custom widgets from context if provided by project's dashboard_callback
|
|
160
154
|
custom_widgets = context.get('custom_widgets', [])
|
|
@@ -172,9 +166,6 @@ class UnfoldCallbacks(
|
|
|
172
166
|
custom_widgets=custom_widgets,
|
|
173
167
|
custom_metrics=custom_metrics
|
|
174
168
|
)
|
|
175
|
-
# Debug: save render (only in debug mode)
|
|
176
|
-
if config and config.debug:
|
|
177
|
-
save_section_render('widgets', widgets_section)
|
|
178
169
|
except Exception as e:
|
|
179
170
|
logger.error(f"Failed to render widgets section: {e}", exc_info=True)
|
|
180
171
|
widgets_section = None
|
|
@@ -202,6 +193,9 @@ class UnfoldCallbacks(
|
|
|
202
193
|
"documentation_section": documentation_section,
|
|
203
194
|
"widgets_section": widgets_section,
|
|
204
195
|
|
|
196
|
+
# Documentation content for commands tab
|
|
197
|
+
"documentation_content": documentation_content,
|
|
198
|
+
|
|
205
199
|
# Statistics cards
|
|
206
200
|
"cards": cards_data,
|
|
207
201
|
"user_stats": [card.to_dict() for card in user_stats],
|
|
@@ -296,6 +290,14 @@ class UnfoldCallbacks(
|
|
|
296
290
|
# activity_tracker_data = context.get('activity_tracker', [])
|
|
297
291
|
# logger.info(f"Activity tracker data count: {len(activity_tracker_data)}")
|
|
298
292
|
|
|
293
|
+
# Debug: save full rendered page (only in debug mode)
|
|
294
|
+
if config and config.debug:
|
|
295
|
+
try:
|
|
296
|
+
full_html = render_to_string('admin/index.html', context, request)
|
|
297
|
+
save_dashboard_render(full_html, name='dashboard_full_page', context=context)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Failed to save full dashboard render: {e}", exc_info=True)
|
|
300
|
+
|
|
299
301
|
return context
|
|
300
302
|
|
|
301
303
|
except Exception as e:
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.77"
|
|
8
8
|
description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constance Settings UI Styles
|
|
3
|
+
*
|
|
4
|
+
* Enhanced UI for Django Constance dynamic settings
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Smooth transitions for all interactive elements */
|
|
8
|
+
.constance-expandable {
|
|
9
|
+
transition: all 0.2s ease-in-out;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Better hover states */
|
|
13
|
+
.constance tr:hover {
|
|
14
|
+
background-color: rgba(0, 0, 0, 0.02);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dark .constance tr:hover {
|
|
18
|
+
background-color: rgba(255, 255, 255, 0.02);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Improved focus states for accessibility */
|
|
22
|
+
.constance a:focus,
|
|
23
|
+
.constance button:focus {
|
|
24
|
+
outline: 2px solid rgb(59, 130, 246);
|
|
25
|
+
outline-offset: 2px;
|
|
26
|
+
border-radius: 4px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Better spacing for long text */
|
|
30
|
+
.constance .break-words {
|
|
31
|
+
word-break: break-word;
|
|
32
|
+
overflow-wrap: break-word;
|
|
33
|
+
hyphens: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Smooth animations for expand/collapse */
|
|
37
|
+
.constance [x-show] {
|
|
38
|
+
transition: opacity 0.2s ease-in-out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Icon rotation animation */
|
|
42
|
+
.constance .material-symbols-outlined {
|
|
43
|
+
transition: transform 0.3s ease-in-out;
|
|
44
|
+
}
|