django-cfg 1.4.87__py3-none-any.whl → 1.4.89__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 (177) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/centrifugo/views/__init__.py +0 -2
  3. django_cfg/apps/dashboard/permissions.py +48 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +8 -1
  5. django_cfg/apps/dashboard/serializers/commands.py +29 -0
  6. django_cfg/apps/dashboard/services/__init__.py +2 -0
  7. django_cfg/{modules/django_unfold/callbacks/base.py → apps/dashboard/services/commands_security.py} +28 -90
  8. django_cfg/apps/dashboard/services/commands_service.py +208 -9
  9. django_cfg/apps/dashboard/services/overview_service.py +205 -0
  10. django_cfg/apps/dashboard/views/commands_views.py +92 -4
  11. django_cfg/apps/frontend/test_routing.py +134 -0
  12. django_cfg/apps/frontend/views.py +73 -28
  13. django_cfg/apps/urls.py +0 -1
  14. django_cfg/core/builders/apps_builder.py +0 -58
  15. django_cfg/modules/django_unfold/__init__.py +5 -24
  16. django_cfg/modules/django_unfold/models/__init__.py +0 -23
  17. django_cfg/modules/django_unfold/models/config.py +11 -65
  18. django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
  19. django_cfg/modules/django_unfold/tailwind.py +2 -4
  20. django_cfg/pyproject.toml +1 -1
  21. django_cfg/registry/third_party.py +0 -9
  22. django_cfg/routing/callbacks.py +1 -43
  23. django_cfg/static/frontend/admin/404/index.html +1 -1
  24. django_cfg/static/frontend/admin/404.html +1 -1
  25. django_cfg/static/frontend/admin/500/index.html +1 -1
  26. django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
  27. django_cfg/static/frontend/admin/_next/static/chunks/{19430.fe7bff7372f8a256.js → 19430.c4c95603c23c17fe.js} +1 -1
  28. django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
  29. django_cfg/static/frontend/admin/_next/static/chunks/94141-bc6d47f419b26b21.js +1 -0
  30. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-c336f254967dd101.js → _app-c7dcd3aa616fab68.js} +6 -6
  31. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-b39c7f22c066e2c6.js → cookies-97d279800f12aab4.js} +1 -1
  32. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-5aedad0cf3a4f80f.js → privacy-1d5e6cd94689247e.js} +1 -1
  33. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-dbd854d0d5d483e2.js → security-55e49700e7a01f5a.js} +1 -1
  34. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-f3e1d2b9e5edf12f.js → terms-14c02bb2d3198352.js} +1 -1
  35. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-22532c65971225eb.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
  36. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-d4ccbe1265cbd853.js +1 -0
  37. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-da114020a6b940f5.js → webpack-5a92f81363b62aa7.js} +1 -1
  38. django_cfg/static/frontend/admin/_next/static/css/3063068f0d5a8a00.css +3 -0
  39. django_cfg/static/frontend/admin/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  40. django_cfg/static/frontend/admin/_next/static/media/21350d82a1f187e9-s.p.woff2 +0 -0
  41. django_cfg/static/frontend/admin/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
  42. django_cfg/static/frontend/admin/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  43. django_cfg/static/frontend/admin/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  44. django_cfg/static/frontend/admin/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  45. django_cfg/static/frontend/admin/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
  46. django_cfg/static/frontend/admin/auth/index.html +1 -1
  47. django_cfg/static/frontend/admin/index.html +1 -1
  48. django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
  49. django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
  50. django_cfg/static/frontend/admin/legal/security/index.html +1 -1
  51. django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
  52. django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
  53. django_cfg/static/frontend/admin/private/index.html +1 -1
  54. django_cfg/static/frontend/admin/private/profile/index.html +1 -1
  55. django_cfg/static/frontend/admin/private/ui/index.html +2 -2
  56. django_cfg/templates/admin/index.html +1 -1
  57. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
  58. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/RECORD +62 -163
  59. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
  60. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
  61. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
  62. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
  63. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
  64. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
  65. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
  66. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
  67. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
  68. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
  69. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
  70. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
  71. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
  72. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
  73. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
  77. django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
  78. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
  79. django_cfg/apps/centrifugo/urls_admin.py +0 -20
  80. django_cfg/apps/centrifugo/views/dashboard.py +0 -28
  81. django_cfg/modules/django_dashboard/__init__.py +0 -23
  82. django_cfg/modules/django_dashboard/components.py +0 -312
  83. django_cfg/modules/django_dashboard/debug.py +0 -174
  84. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  85. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  86. django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
  87. django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
  88. django_cfg/modules/django_dashboard/sections/base.py +0 -129
  89. django_cfg/modules/django_dashboard/sections/commands.py +0 -33
  90. django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
  91. django_cfg/modules/django_dashboard/sections/overview.py +0 -398
  92. django_cfg/modules/django_dashboard/sections/stats.py +0 -48
  93. django_cfg/modules/django_dashboard/sections/system.py +0 -74
  94. django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
  95. django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
  96. django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
  97. django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
  98. django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
  99. django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
  100. django_cfg/modules/django_unfold/callbacks/main.py +0 -322
  101. django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
  102. django_cfg/modules/django_unfold/callbacks/system.py +0 -180
  103. django_cfg/modules/django_unfold/callbacks/users.py +0 -65
  104. django_cfg/modules/django_unfold/models/dashboard.py +0 -207
  105. django_cfg/modules/django_unfold/models/tabs.py +0 -26
  106. django_cfg/modules/django_unfold/models.py +0 -98
  107. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
  108. django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +0 -1
  109. django_cfg/static/frontend/admin/_next/static/chunks/50314-48bd5701f62faf27.js +0 -1
  110. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +0 -1
  111. django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +0 -3
  112. django_cfg/static/frontend/admin/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
  113. django_cfg/static/frontend/admin/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
  114. django_cfg/static/frontend/admin/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
  115. django_cfg/static/frontend/admin/_next/static/media/875ae681bfde4580-s.p.woff2 +0 -0
  116. django_cfg/static/frontend/admin/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
  117. django_cfg/static/frontend/admin/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
  118. django_cfg/templates/admin/sections/commands_section.html +0 -5
  119. django_cfg/templates/admin/sections/documentation_section.html +0 -5
  120. django_cfg/templates/admin/sections/overview_section.html +0 -5
  121. django_cfg/templates/admin/sections/stats_section.html +0 -5
  122. django_cfg/templates/admin/sections/system_section.html +0 -5
  123. django_cfg/templates/admin/sections/widgets_section.html +0 -11
  124. django_cfg/templates/admin_old/components/action_grid.html +0 -49
  125. django_cfg/templates/admin_old/components/card.html +0 -50
  126. django_cfg/templates/admin_old/components/data_table.html +0 -67
  127. django_cfg/templates/admin_old/components/metric_card.html +0 -39
  128. django_cfg/templates/admin_old/components/modal.html +0 -58
  129. django_cfg/templates/admin_old/components/progress_bar.html +0 -20
  130. django_cfg/templates/admin_old/components/section_header.html +0 -26
  131. django_cfg/templates/admin_old/components/stat_item.html +0 -32
  132. django_cfg/templates/admin_old/components/stats_grid.html +0 -72
  133. django_cfg/templates/admin_old/components/status_badge.html +0 -28
  134. django_cfg/templates/admin_old/components/user_avatar.html +0 -27
  135. django_cfg/templates/admin_old/constance/change_list.html +0 -74
  136. django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
  137. django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
  138. django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
  139. django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
  140. django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
  141. django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
  142. django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
  143. django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
  144. django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
  145. django_cfg/templates/admin_old/index.html +0 -80
  146. django_cfg/templates/admin_old/index_new.html +0 -119
  147. django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
  148. django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
  149. django_cfg/templates/admin_old/sections/commands_section.html +0 -549
  150. django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
  151. django_cfg/templates/admin_old/sections/overview_section.html +0 -112
  152. django_cfg/templates/admin_old/sections/stats_section.html +0 -35
  153. django_cfg/templates/admin_old/sections/system_section.html +0 -99
  154. django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
  155. django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
  156. django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
  157. django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
  158. django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
  159. django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
  160. django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
  161. django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
  162. django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
  163. django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
  164. django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
  165. django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
  166. django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
  167. django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
  168. django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
  169. django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
  170. django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
  171. django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
  172. django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
  173. django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
  174. /django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
  175. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
  176. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
  177. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
@@ -1,109 +0,0 @@
1
- """
2
- Management command to debug dashboard rendering.
3
-
4
- Renders all dashboard sections and saves them for inspection.
5
- """
6
-
7
- from django.contrib.auth import get_user_model
8
- from django.core.management.base import BaseCommand
9
- from django.test import RequestFactory
10
-
11
- from django_cfg.modules.django_dashboard.debug import get_debugger
12
- from django_cfg.modules.django_dashboard.sections.commands import CommandsSection
13
- from django_cfg.modules.django_dashboard.sections.overview import OverviewSection
14
- from django_cfg.modules.django_dashboard.sections.stats import StatsSection
15
- from django_cfg.modules.django_dashboard.sections.system import SystemSection
16
-
17
-
18
- class Command(BaseCommand):
19
- help = "Debug dashboard rendering - saves all sections to disk"
20
-
21
- def add_arguments(self, parser):
22
- parser.add_argument(
23
- '--section',
24
- type=str,
25
- choices=['overview', 'stats', 'system', 'commands', 'all'],
26
- default='all',
27
- help='Which section to render (default: all)'
28
- )
29
- parser.add_argument(
30
- '--user',
31
- type=str,
32
- help='Username to use for request (default: first superuser)'
33
- )
34
-
35
- def handle(self, *args, **options):
36
- # Create mock request
37
- factory = RequestFactory()
38
- request = factory.get('/admin/')
39
-
40
- # Get user for request
41
- User = get_user_model()
42
- username = options.get('user')
43
-
44
- if username:
45
- try:
46
- user = User.objects.get(username=username)
47
- except User.DoesNotExist:
48
- self.stdout.write(
49
- self.style.ERROR(f"User '{username}' not found")
50
- )
51
- return
52
- else:
53
- # Use first superuser
54
- user = User.objects.filter(is_superuser=True).first()
55
- if not user:
56
- self.stdout.write(
57
- self.style.WARNING("No superuser found, using anonymous request")
58
- )
59
-
60
- if user:
61
- request.user = user
62
- self.stdout.write(f"Using user: {user.username}")
63
-
64
- # Get debugger
65
- debugger = get_debugger()
66
- self.stdout.write(f"Saving renders to: {debugger.output_dir}")
67
-
68
- section_choice = options['section']
69
- sections = {
70
- 'overview': OverviewSection,
71
- 'stats': StatsSection,
72
- 'system': SystemSection,
73
- 'commands': CommandsSection,
74
- }
75
-
76
- if section_choice == 'all':
77
- sections_to_render = sections.items()
78
- else:
79
- sections_to_render = [(section_choice, sections[section_choice])]
80
-
81
- # Render sections
82
- for name, SectionClass in sections_to_render:
83
- self.stdout.write(f"\nRendering {name} section...")
84
-
85
- try:
86
- section = SectionClass(request)
87
- html = section.render()
88
-
89
- # Save render
90
- path = debugger.save_section_render(
91
- section_name=name,
92
- html=html,
93
- section_data=section.get_context_data() if hasattr(section, 'get_context_data') else None
94
- )
95
-
96
- self.stdout.write(
97
- self.style.SUCCESS(f"✅ {name}: {len(html)} bytes, saved to {path.name}")
98
- )
99
-
100
- except Exception as e:
101
- self.stdout.write(
102
- self.style.ERROR(f"❌ {name}: {e}")
103
- )
104
- import traceback
105
- traceback.print_exc()
106
-
107
- self.stdout.write(
108
- self.style.SUCCESS(f"\n✅ Done! Check renders in: {debugger.output_dir}")
109
- )
@@ -1 +0,0 @@
1
- """Dashboard sections package."""
@@ -1,129 +0,0 @@
1
- """
2
- Base dashboard section classes.
3
-
4
- Provides foundation for dashboard sections following Unfold's pattern.
5
- """
6
-
7
- from typing import Any, Dict, Optional
8
-
9
- from django.http import HttpRequest
10
- from django.template.loader import render_to_string
11
-
12
-
13
- class DashboardSection:
14
- """
15
- Base class for dashboard sections.
16
-
17
- Each section is responsible for:
18
- - Data collection
19
- - Context preparation
20
- - Template rendering
21
-
22
- Usage:
23
- class MySection(DashboardSection):
24
- template_name = "admin/sections/my_section.html"
25
-
26
- def get_context_data(self, **kwargs):
27
- context = super().get_context_data(**kwargs)
28
- context['custom_data'] = self.get_custom_data()
29
- return context
30
- """
31
-
32
- template_name: Optional[str] = None
33
- title: Optional[str] = None
34
- icon: Optional[str] = None
35
-
36
- def __init__(self, request: HttpRequest) -> None:
37
- """Initialize section with request context."""
38
- self.request = request
39
-
40
- def get_context_data(self, **kwargs) -> Dict[str, Any]:
41
- """
42
- Get context data for template rendering.
43
-
44
- Override this method to add custom context.
45
- """
46
- context = {
47
- 'request': self.request,
48
- 'section': self,
49
- }
50
-
51
- if self.title:
52
- context['title'] = self.title
53
-
54
- if self.icon:
55
- context['icon'] = self.icon
56
-
57
- context.update(kwargs)
58
- return context
59
-
60
- def render(self, **kwargs) -> str:
61
- """
62
- Render the section template.
63
-
64
- Args:
65
- **kwargs: Additional context to pass to template
66
-
67
- Returns:
68
- Rendered HTML string
69
- """
70
- if not self.template_name:
71
- raise NotImplementedError(
72
- f"{self.__class__.__name__} must define 'template_name' or override render()"
73
- )
74
-
75
- context = self.get_context_data(**kwargs)
76
- return render_to_string(self.template_name, context=context, request=self.request)
77
-
78
- def __str__(self) -> str:
79
- """String representation."""
80
- return self.title or self.__class__.__name__
81
-
82
-
83
- class DataSection(DashboardSection):
84
- """
85
- Section that provides data-driven content.
86
-
87
- Extend this for sections that need to fetch and process data.
88
- """
89
-
90
- def get_data(self) -> Any:
91
- """
92
- Get section data.
93
-
94
- Override this to provide section-specific data.
95
- """
96
- raise NotImplementedError(
97
- f"{self.__class__.__name__} must implement get_data()"
98
- )
99
-
100
- def get_context_data(self, **kwargs) -> Dict[str, Any]:
101
- """Add data to context."""
102
- context = super().get_context_data(**kwargs)
103
- context['data'] = self.get_data()
104
- return context
105
-
106
-
107
- class CardSection(DashboardSection):
108
- """
109
- Section rendered as a card component.
110
-
111
- Automatically wraps content in card template.
112
- """
113
-
114
- template_name = "admin/components/card.html"
115
- content_template: Optional[str] = None
116
-
117
- def get_context_data(self, **kwargs) -> Dict[str, Any]:
118
- """Add card-specific context."""
119
- context = super().get_context_data(**kwargs)
120
-
121
- # Render content if content_template is provided
122
- if self.content_template:
123
- context['content'] = render_to_string(
124
- self.content_template,
125
- context=context,
126
- request=self.request
127
- )
128
-
129
- return context
@@ -1,33 +0,0 @@
1
- """Commands section for dashboard."""
2
-
3
- from typing import Any, Dict
4
-
5
- from .base import DataSection
6
-
7
-
8
- class CommandsSection(DataSection):
9
- """
10
- Management commands section.
11
- """
12
-
13
- template_name = "admin/sections/commands_section.html"
14
- title = "Management Commands"
15
- icon = "terminal"
16
-
17
- def get_data(self) -> Dict[str, Any]:
18
- """Get commands data."""
19
- from django_cfg.modules.django_unfold.callbacks.base import (
20
- get_available_commands,
21
- get_commands_by_category,
22
- )
23
-
24
- commands = get_available_commands()
25
- categorized = get_commands_by_category()
26
-
27
- return {
28
- 'commands': commands,
29
- 'categories': categorized,
30
- 'total_commands': len(commands),
31
- 'core_commands': len([cmd for cmd in commands if cmd.get('is_core')]),
32
- 'custom_commands': len([cmd for cmd in commands if cmd.get('is_custom')]),
33
- }
@@ -1,393 +0,0 @@
1
- """Documentation section for dashboard."""
2
-
3
- import inspect
4
- import os
5
- from pathlib import Path
6
- from typing import Any, Dict, List
7
-
8
- from django.core.management import get_commands, load_command_class
9
-
10
- from .base import DataSection
11
-
12
-
13
- class DocumentationSection(DataSection):
14
- """
15
- Management commands documentation section.
16
-
17
- Displays the README.md file from django_cfg/management/ directory.
18
- """
19
-
20
- template_name = "admin/sections/documentation_section.html"
21
- title = "Commands Documentation"
22
- icon = "description"
23
-
24
- def get_context_data(self, **kwargs) -> Dict[str, Any]:
25
- """Add documentation data directly to context (not nested under 'data')."""
26
- # Call parent but skip DataSection's get_data() nesting
27
- context = super(DataSection, self).get_context_data(**kwargs)
28
- # Add our data directly to context
29
- context.update(self.get_data())
30
- return context
31
-
32
- def find_module_readme(self, module_name: str) -> str:
33
- """Find and read README.md from module's management directory."""
34
- import logging
35
- logger = logging.getLogger(__name__)
36
-
37
- try:
38
- import django_cfg
39
- django_cfg_path = Path(django_cfg.__file__).parent
40
-
41
- # Try different paths
42
- possible_paths = [
43
- django_cfg_path / 'modules' / module_name / 'management' / 'README.md',
44
- django_cfg_path / 'management' / 'commands' / module_name / 'README.md',
45
- ]
46
-
47
- for readme_path in possible_paths:
48
- if readme_path.exists():
49
- logger.info(f"Found module README at: {readme_path}")
50
- with open(readme_path, encoding='utf-8') as f:
51
- return f.read()
52
- except Exception as e:
53
- logger.debug(f"Could not find README for module {module_name}: {e}")
54
-
55
- return ""
56
-
57
- def get_commands_structure(self) -> Dict[str, List[Dict[str, Any]]]:
58
- """Get structured command data grouped by app."""
59
- import logging
60
- logger = logging.getLogger(__name__)
61
-
62
- # Get all management commands
63
- commands = get_commands()
64
-
65
- # Filter only django_cfg commands
66
- django_cfg_commands = {
67
- name: app for name, app in commands.items()
68
- if 'django_cfg' in app
69
- }
70
-
71
- logger.info(f"Found {len(django_cfg_commands)} django_cfg commands")
72
-
73
- # Group commands by app
74
- commands_by_app = {}
75
-
76
- for cmd_name, app_name in sorted(django_cfg_commands.items()):
77
- try:
78
- # Load the command class
79
- try:
80
- command = load_command_class(app_name, cmd_name)
81
- except (ImportError, ModuleNotFoundError) as e:
82
- logger.debug(f"Skipping command {cmd_name}: module not found - {e}")
83
- continue
84
-
85
- # Use app_name as group key
86
- if app_name not in commands_by_app:
87
- # Create display name from app_name
88
- display_name = app_name.replace('django_cfg.modules.', '').replace('django_cfg', 'Django CFG').replace('_', ' ').title()
89
-
90
- commands_by_app[app_name] = {
91
- 'name': app_name,
92
- 'display_name': display_name,
93
- 'commands': []
94
- }
95
-
96
- # Extract command information
97
- command_info = {
98
- 'name': cmd_name,
99
- 'help': getattr(command, 'help', 'No description available'),
100
- 'docstring': inspect.cleandoc(command.__doc__) if command.__doc__ else '',
101
- 'web_executable': getattr(command, 'web_executable', None),
102
- 'requires_input': getattr(command, 'requires_input', None),
103
- 'is_destructive': getattr(command, 'is_destructive', None),
104
- 'arguments': []
105
- }
106
-
107
- # Get arguments
108
- try:
109
- parser = command.create_parser('', cmd_name)
110
- actions = [action for action in parser._actions if action.dest != 'help']
111
-
112
- for action in actions:
113
- if action.option_strings:
114
- command_info['arguments'].append({
115
- 'options': action.option_strings,
116
- 'help': action.help or 'No description',
117
- 'default': action.default if action.default != '==SUPPRESS==' else None,
118
- 'required': getattr(action, 'required', False),
119
- })
120
- except Exception as e:
121
- logger.debug(f"Could not extract arguments for {cmd_name}: {e}")
122
-
123
- commands_by_app[app_name]['commands'].append(command_info)
124
-
125
- except Exception as e:
126
- logger.error(f"Error loading command {cmd_name}: {e}")
127
- continue
128
-
129
- return commands_by_app
130
-
131
- def generate_documentation_from_commands(self) -> str:
132
- """Generate markdown documentation from command docstrings and module READMEs."""
133
- import logging
134
- logger = logging.getLogger(__name__)
135
-
136
- markdown_lines = []
137
- markdown_lines.append("# Django-CFG Management Commands\n\n")
138
-
139
- # Check for main README.md
140
- try:
141
- import django_cfg
142
- django_cfg_path = Path(django_cfg.__file__).parent
143
- main_readme_path = django_cfg_path / 'management' / 'README.md'
144
-
145
- if main_readme_path.exists():
146
- logger.info("Found main management README.md, using it as primary documentation")
147
- with open(main_readme_path, encoding='utf-8') as f:
148
- return f.read() # Use existing README if it exists
149
- except Exception as e:
150
- logger.debug(f"No main README found, continuing with auto-generation: {e}")
151
-
152
- markdown_lines.append("_Auto-generated documentation from command docstrings and module READMEs._\n\n")
153
-
154
- # Get all management commands
155
- commands = get_commands()
156
-
157
- # Filter only django_cfg commands
158
- django_cfg_commands = {
159
- name: app for name, app in commands.items()
160
- if 'django_cfg' in app
161
- }
162
-
163
- logger.info(f"Found {len(django_cfg_commands)} django_cfg commands")
164
-
165
- # Group commands by module
166
- commands_by_module = {}
167
- module_paths = {} # Store module paths for README lookup
168
-
169
- for cmd_name, app_name in sorted(django_cfg_commands.items()):
170
- try:
171
- # Load the command class
172
- command = load_command_class(app_name, cmd_name)
173
-
174
- # Get module path
175
- module_path = command.__module__
176
- if 'modules' in module_path:
177
- # Extract module name like 'django_ngrok', 'django_tasks', etc.
178
- parts = module_path.split('.')
179
- if 'modules' in parts:
180
- module_idx = parts.index('modules')
181
- if len(parts) > module_idx + 1:
182
- module_name = parts[module_idx + 1]
183
- else:
184
- module_name = 'core'
185
- else:
186
- module_name = 'core'
187
- else:
188
- module_name = 'core'
189
-
190
- if module_name not in commands_by_module:
191
- commands_by_module[module_name] = []
192
- module_paths[module_name] = module_path
193
-
194
- commands_by_module[module_name].append((cmd_name, command))
195
-
196
- except Exception as e:
197
- logger.error(f"Error loading command {cmd_name}: {e}")
198
- continue
199
-
200
- # Generate documentation for each module
201
- for module_name in sorted(commands_by_module.keys()):
202
- markdown_lines.append(f"\n## {module_name.replace('_', ' ').title()} Commands\n\n")
203
-
204
- # Check for module-specific README
205
- module_readme = self.find_module_readme(module_name)
206
- if module_readme:
207
- markdown_lines.append(f"_{module_name} module documentation:_\n\n")
208
- markdown_lines.append(module_readme)
209
- markdown_lines.append("\n\n### Available Commands\n\n")
210
-
211
- for cmd_name, command_class in sorted(commands_by_module[module_name]):
212
- markdown_lines.append(f"\n#### `{cmd_name}`\n\n")
213
-
214
- # Get help text
215
- if hasattr(command_class, 'help'):
216
- markdown_lines.append(f"{command_class.help}\n\n")
217
-
218
- # Get class docstring
219
- if command_class.__doc__:
220
- doc = inspect.cleandoc(command_class.__doc__)
221
- markdown_lines.append(f"{doc}\n\n")
222
-
223
- # Get metadata
224
- metadata = []
225
- if hasattr(command_class, 'web_executable'):
226
- metadata.append(f"**Web Executable**: {'Yes' if command_class.web_executable else 'No'}")
227
- if hasattr(command_class, 'requires_input'):
228
- metadata.append(f"**Requires Input**: {'Yes' if command_class.requires_input else 'No'}")
229
- if hasattr(command_class, 'is_destructive'):
230
- metadata.append(f"**Destructive**: {'Yes' if command_class.is_destructive else 'No'}")
231
-
232
- if metadata:
233
- markdown_lines.append("**Metadata:**\n\n")
234
- for meta in metadata:
235
- markdown_lines.append(f"- {meta}\n")
236
- markdown_lines.append("\n")
237
-
238
- # Get arguments
239
- try:
240
- parser = command_class.create_parser('', cmd_name)
241
- actions = [action for action in parser._actions if action.dest != 'help']
242
-
243
- if actions:
244
- markdown_lines.append("**Arguments:**\n\n")
245
- for action in actions:
246
- if action.option_strings:
247
- opts = ', '.join(f"`{opt}`" for opt in action.option_strings)
248
- help_text = action.help or 'No description'
249
- markdown_lines.append(f"- {opts}: {help_text}\n")
250
- markdown_lines.append("\n")
251
- except Exception as e:
252
- logger.debug(f"Could not extract arguments for {cmd_name}: {e}")
253
-
254
- # Add usage example if available in docstring
255
- if command_class.__doc__ and '```' in command_class.__doc__:
256
- markdown_lines.append("**Example:**\n\n")
257
-
258
- return ''.join(markdown_lines)
259
-
260
- def get_data(self) -> Dict[str, Any]:
261
- """Get structured command documentation data."""
262
- import logging
263
- logger = logging.getLogger(__name__)
264
-
265
- try:
266
- commands_structure = self.get_commands_structure()
267
-
268
- return {
269
- 'commands_by_module': commands_structure,
270
- 'total_commands': sum(len(app['commands']) for app in commands_structure.values()),
271
- 'total_modules': len(commands_structure),
272
- }
273
- except Exception as e:
274
- logger.error(f"Error generating command structure: {e}", exc_info=True)
275
- return {
276
- 'commands_by_module': {},
277
- 'total_commands': 0,
278
- 'total_modules': 0,
279
- 'error': str(e)
280
- }
281
-
282
- def get_data_old(self) -> Dict[str, Any]:
283
- """DEPRECATED: Get documentation content from README.md or auto-generate from docstrings."""
284
- import logging
285
- logger = logging.getLogger(__name__)
286
-
287
- # Check if we should use auto-generation
288
- use_autogen = os.environ.get('DJANGO_CFG_AUTOGEN_DOCS', 'true').lower() == 'true'
289
-
290
- readme_content = ""
291
- documentation_html = ""
292
- readme_path = None
293
- readme_exists = False
294
-
295
- if use_autogen:
296
- # Auto-generate documentation from command docstrings
297
- logger.info("Auto-generating documentation from command docstrings")
298
- try:
299
- readme_content = self.generate_documentation_from_commands()
300
-
301
- # Convert to HTML
302
- try:
303
- import markdown
304
- documentation_html = markdown.markdown(
305
- readme_content,
306
- extensions=[
307
- 'markdown.extensions.fenced_code',
308
- 'markdown.extensions.tables',
309
- 'markdown.extensions.toc',
310
- 'markdown.extensions.nl2br',
311
- 'markdown.extensions.sane_lists',
312
- ]
313
- )
314
- logger.info("Auto-generated documentation rendered successfully")
315
- except ImportError:
316
- logger.warning("Markdown library not available, will display as plain text")
317
-
318
- readme_exists = True
319
- readme_path = "Auto-generated from docstrings"
320
-
321
- except Exception as e:
322
- logger.error(f"Error auto-generating documentation: {e}", exc_info=True)
323
- readme_content = f"Error auto-generating documentation: {str(e)}"
324
- else:
325
- # Use static README.md file
326
- # Try multiple path resolution strategies
327
- possible_paths = [
328
- # Strategy 1: Relative to this file
329
- Path(__file__).parent.parent.parent / 'management' / 'README.md',
330
- # Strategy 2: Using django_cfg module location
331
- Path(__file__).parent.parent.parent / 'management' / 'README.md',
332
- # Strategy 3: Absolute path for django_cfg package
333
- Path('/Users/markinmatrix/Documents/htdocs/@CARAPIS/encar_parser_new/@projects/django-cfg/projects/django-cfg-dev/src/django_cfg/management/README.md'),
334
- ]
335
-
336
- # Find the first existing path
337
- for path in possible_paths:
338
- if path.exists():
339
- readme_path = path
340
- logger.info(f"Found README.md at: {readme_path}")
341
- break
342
-
343
- # If still not found, try using django_cfg module import
344
- if not readme_path or not readme_path.exists():
345
- try:
346
- import django_cfg
347
- django_cfg_path = Path(django_cfg.__file__).parent
348
- readme_path = django_cfg_path / 'management' / 'README.md'
349
- logger.info(f"Using django_cfg module path: {readme_path}")
350
- except Exception as e:
351
- logger.error(f"Error finding django_cfg module: {e}")
352
- readme_path = possible_paths[0] # Fallback to first path
353
-
354
- try:
355
- if readme_path and readme_path.exists():
356
- logger.info(f"Reading README from: {readme_path}")
357
- with open(readme_path, encoding='utf-8') as f:
358
- readme_content = f.read()
359
- logger.info(f"README content loaded: {len(readme_content)} characters")
360
-
361
- # Try to convert markdown to HTML
362
- try:
363
- import markdown
364
- documentation_html = markdown.markdown(
365
- readme_content,
366
- extensions=[
367
- 'markdown.extensions.fenced_code',
368
- 'markdown.extensions.tables',
369
- 'markdown.extensions.toc',
370
- 'markdown.extensions.nl2br',
371
- 'markdown.extensions.sane_lists',
372
- ]
373
- )
374
- logger.info("Markdown rendered successfully")
375
- except ImportError:
376
- logger.warning("Markdown library not available, will display as plain text")
377
- pass
378
-
379
- readme_exists = True
380
- else:
381
- logger.error(f"README.md not found at: {readme_path}")
382
- readme_content = "README.md not found. Searched paths:\n" + "\n".join(str(p) for p in possible_paths)
383
-
384
- except Exception as e:
385
- logger.error(f"Error loading documentation: {e}", exc_info=True)
386
- readme_content = f"Error loading documentation: {str(e)}"
387
-
388
- return {
389
- 'readme_content': readme_content,
390
- 'documentation_content': documentation_html if documentation_html else None,
391
- 'readme_path': str(readme_path) if readme_path else 'Not found',
392
- 'readme_exists': readme_exists,
393
- }