django-cfg 1.1.82__py3-none-any.whl → 1.2.1__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 (244) hide show
  1. django_cfg/__init__.py +20 -448
  2. django_cfg/apps/accounts/README.md +3 -3
  3. django_cfg/apps/accounts/admin/__init__.py +0 -2
  4. django_cfg/apps/accounts/admin/activity.py +2 -9
  5. django_cfg/apps/accounts/admin/filters.py +0 -42
  6. django_cfg/apps/accounts/admin/inlines.py +8 -8
  7. django_cfg/apps/accounts/admin/otp.py +5 -5
  8. django_cfg/apps/accounts/admin/registration_source.py +1 -8
  9. django_cfg/apps/accounts/admin/user.py +12 -20
  10. django_cfg/apps/accounts/managers/user_manager.py +2 -129
  11. django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
  12. django_cfg/apps/accounts/models.py +3 -123
  13. django_cfg/apps/accounts/serializers/otp.py +40 -44
  14. django_cfg/apps/accounts/serializers/profile.py +0 -2
  15. django_cfg/apps/accounts/services/otp_service.py +98 -186
  16. django_cfg/apps/accounts/signals.py +25 -15
  17. django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
  18. django_cfg/apps/accounts/views/otp.py +35 -36
  19. django_cfg/apps/agents/README.md +129 -0
  20. django_cfg/apps/agents/__init__.py +68 -0
  21. django_cfg/apps/agents/admin/__init__.py +17 -0
  22. django_cfg/apps/agents/admin/execution_admin.py +460 -0
  23. django_cfg/apps/agents/admin/registry_admin.py +360 -0
  24. django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
  25. django_cfg/apps/agents/apps.py +29 -0
  26. django_cfg/apps/agents/core/__init__.py +20 -0
  27. django_cfg/apps/agents/core/agent.py +281 -0
  28. django_cfg/apps/agents/core/dependencies.py +154 -0
  29. django_cfg/apps/agents/core/exceptions.py +66 -0
  30. django_cfg/apps/agents/core/models.py +106 -0
  31. django_cfg/apps/agents/core/orchestrator.py +391 -0
  32. django_cfg/apps/agents/examples/__init__.py +3 -0
  33. django_cfg/apps/agents/examples/simple_example.py +161 -0
  34. django_cfg/apps/agents/integration/__init__.py +14 -0
  35. django_cfg/apps/agents/integration/middleware.py +80 -0
  36. django_cfg/apps/agents/integration/registry.py +345 -0
  37. django_cfg/apps/agents/integration/signals.py +50 -0
  38. django_cfg/apps/agents/management/__init__.py +3 -0
  39. django_cfg/apps/agents/management/commands/__init__.py +3 -0
  40. django_cfg/apps/agents/management/commands/create_agent.py +365 -0
  41. django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
  42. django_cfg/apps/agents/managers/__init__.py +23 -0
  43. django_cfg/apps/agents/managers/execution.py +236 -0
  44. django_cfg/apps/agents/managers/registry.py +254 -0
  45. django_cfg/apps/agents/managers/toolsets.py +496 -0
  46. django_cfg/apps/agents/migrations/0001_initial.py +286 -0
  47. django_cfg/apps/agents/migrations/__init__.py +5 -0
  48. django_cfg/apps/agents/models/__init__.py +15 -0
  49. django_cfg/apps/agents/models/execution.py +215 -0
  50. django_cfg/apps/agents/models/registry.py +220 -0
  51. django_cfg/apps/agents/models/toolsets.py +305 -0
  52. django_cfg/apps/agents/patterns/__init__.py +24 -0
  53. django_cfg/apps/agents/patterns/content_agents.py +234 -0
  54. django_cfg/apps/agents/toolsets/__init__.py +15 -0
  55. django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
  56. django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
  57. django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
  58. django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
  59. django_cfg/apps/agents/urls.py +46 -0
  60. django_cfg/apps/knowbase/README.md +150 -0
  61. django_cfg/apps/knowbase/__init__.py +27 -0
  62. django_cfg/apps/knowbase/admin/__init__.py +23 -0
  63. django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
  64. django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
  65. django_cfg/apps/knowbase/admin/document_admin.py +650 -0
  66. django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
  67. django_cfg/apps/knowbase/apps.py +81 -0
  68. django_cfg/apps/knowbase/config/README.md +176 -0
  69. django_cfg/apps/knowbase/config/__init__.py +51 -0
  70. django_cfg/apps/knowbase/config/constance_fields.py +186 -0
  71. django_cfg/apps/knowbase/config/constance_settings.py +200 -0
  72. django_cfg/apps/knowbase/config/settings.py +450 -0
  73. django_cfg/apps/knowbase/examples/__init__.py +3 -0
  74. django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
  75. django_cfg/apps/knowbase/management/__init__.py +0 -0
  76. django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
  77. django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
  78. django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
  79. django_cfg/apps/knowbase/managers/__init__.py +22 -0
  80. django_cfg/apps/knowbase/managers/archive.py +426 -0
  81. django_cfg/apps/knowbase/managers/base.py +32 -0
  82. django_cfg/apps/knowbase/managers/chat.py +141 -0
  83. django_cfg/apps/knowbase/managers/document.py +203 -0
  84. django_cfg/apps/knowbase/managers/external_data.py +471 -0
  85. django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
  86. django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
  87. django_cfg/apps/knowbase/migrations/__init__.py +5 -0
  88. django_cfg/apps/knowbase/mixins/__init__.py +15 -0
  89. django_cfg/apps/knowbase/mixins/config.py +108 -0
  90. django_cfg/apps/knowbase/mixins/creator.py +81 -0
  91. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
  92. django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
  93. django_cfg/apps/knowbase/mixins/service.py +362 -0
  94. django_cfg/apps/knowbase/models/__init__.py +41 -0
  95. django_cfg/apps/knowbase/models/archive.py +599 -0
  96. django_cfg/apps/knowbase/models/base.py +58 -0
  97. django_cfg/apps/knowbase/models/chat.py +157 -0
  98. django_cfg/apps/knowbase/models/document.py +267 -0
  99. django_cfg/apps/knowbase/models/external_data.py +376 -0
  100. django_cfg/apps/knowbase/serializers/__init__.py +68 -0
  101. django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
  102. django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
  103. django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
  104. django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
  105. django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
  106. django_cfg/apps/knowbase/services/__init__.py +40 -0
  107. django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
  108. django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
  109. django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
  110. django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
  111. django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
  112. django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
  113. django_cfg/apps/knowbase/services/base.py +53 -0
  114. django_cfg/apps/knowbase/services/chat_service.py +239 -0
  115. django_cfg/apps/knowbase/services/document_service.py +144 -0
  116. django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
  117. django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
  118. django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
  119. django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
  120. django_cfg/apps/knowbase/services/embedding/models.py +229 -0
  121. django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
  122. django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
  123. django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
  124. django_cfg/apps/knowbase/services/search_service.py +293 -0
  125. django_cfg/apps/knowbase/signals/__init__.py +21 -0
  126. django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
  127. django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
  128. django_cfg/apps/knowbase/signals/document_signals.py +143 -0
  129. django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
  130. django_cfg/apps/knowbase/tasks/__init__.py +39 -0
  131. django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
  132. django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
  133. django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
  134. django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
  135. django_cfg/apps/knowbase/urls.py +43 -0
  136. django_cfg/apps/knowbase/utils/__init__.py +12 -0
  137. django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
  138. django_cfg/apps/knowbase/utils/text_processing.py +375 -0
  139. django_cfg/apps/knowbase/utils/validation.py +99 -0
  140. django_cfg/apps/knowbase/views/__init__.py +28 -0
  141. django_cfg/apps/knowbase/views/archive_views.py +469 -0
  142. django_cfg/apps/knowbase/views/base.py +49 -0
  143. django_cfg/apps/knowbase/views/chat_views.py +181 -0
  144. django_cfg/apps/knowbase/views/document_views.py +183 -0
  145. django_cfg/apps/knowbase/views/public_views.py +129 -0
  146. django_cfg/apps/leads/admin.py +70 -0
  147. django_cfg/apps/newsletter/admin.py +234 -0
  148. django_cfg/apps/newsletter/admin_filters.py +124 -0
  149. django_cfg/apps/support/admin.py +196 -0
  150. django_cfg/apps/support/admin_filters.py +71 -0
  151. django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
  152. django_cfg/apps/urls.py +5 -4
  153. django_cfg/cli/README.md +1 -1
  154. django_cfg/cli/commands/create_project.py +2 -2
  155. django_cfg/cli/commands/info.py +1 -1
  156. django_cfg/config.py +44 -0
  157. django_cfg/core/config.py +29 -82
  158. django_cfg/core/environment.py +1 -1
  159. django_cfg/core/generation.py +19 -107
  160. django_cfg/{integration.py → core/integration.py} +18 -16
  161. django_cfg/core/validation.py +1 -1
  162. django_cfg/management/__init__.py +1 -1
  163. django_cfg/management/commands/__init__.py +1 -1
  164. django_cfg/management/commands/auto_generate.py +482 -0
  165. django_cfg/management/commands/migrator.py +19 -101
  166. django_cfg/management/commands/test_email.py +1 -1
  167. django_cfg/middleware/README.md +0 -158
  168. django_cfg/middleware/__init__.py +0 -2
  169. django_cfg/middleware/user_activity.py +3 -3
  170. django_cfg/models/api.py +145 -0
  171. django_cfg/models/base.py +287 -0
  172. django_cfg/models/cache.py +4 -4
  173. django_cfg/models/constance.py +25 -88
  174. django_cfg/models/database.py +9 -9
  175. django_cfg/models/drf.py +3 -36
  176. django_cfg/models/email.py +163 -0
  177. django_cfg/models/environment.py +276 -0
  178. django_cfg/models/limits.py +1 -1
  179. django_cfg/models/logging.py +366 -0
  180. django_cfg/models/revolution.py +41 -2
  181. django_cfg/models/security.py +125 -0
  182. django_cfg/models/services.py +1 -1
  183. django_cfg/modules/__init__.py +2 -56
  184. django_cfg/modules/base.py +78 -52
  185. django_cfg/modules/django_currency/service.py +2 -2
  186. django_cfg/modules/django_email.py +2 -2
  187. django_cfg/modules/django_health.py +267 -0
  188. django_cfg/modules/django_llm/llm/client.py +91 -19
  189. django_cfg/modules/django_llm/translator/translator.py +2 -2
  190. django_cfg/modules/django_logger.py +2 -2
  191. django_cfg/modules/django_ngrok.py +2 -2
  192. django_cfg/modules/django_tasks.py +68 -3
  193. django_cfg/modules/django_telegram.py +3 -3
  194. django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
  195. django_cfg/modules/django_twilio/service.py +2 -2
  196. django_cfg/modules/django_twilio/simple_service.py +2 -2
  197. django_cfg/modules/django_twilio/twilio_service.py +2 -2
  198. django_cfg/modules/django_unfold/__init__.py +69 -0
  199. django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
  200. django_cfg/modules/django_unfold/dashboard.py +278 -0
  201. django_cfg/modules/django_unfold/icons/README.md +145 -0
  202. django_cfg/modules/django_unfold/icons/__init__.py +12 -0
  203. django_cfg/modules/django_unfold/icons/constants.py +2851 -0
  204. django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
  205. django_cfg/modules/django_unfold/models/__init__.py +42 -0
  206. django_cfg/modules/django_unfold/models/config.py +601 -0
  207. django_cfg/modules/django_unfold/models/dashboard.py +206 -0
  208. django_cfg/modules/django_unfold/models/dropdown.py +40 -0
  209. django_cfg/modules/django_unfold/models/navigation.py +73 -0
  210. django_cfg/modules/django_unfold/models/tabs.py +25 -0
  211. django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
  212. django_cfg/modules/django_unfold/utils.py +140 -0
  213. django_cfg/registry/__init__.py +23 -0
  214. django_cfg/registry/core.py +61 -0
  215. django_cfg/registry/exceptions.py +11 -0
  216. django_cfg/registry/modules.py +12 -0
  217. django_cfg/registry/services.py +26 -0
  218. django_cfg/registry/third_party.py +52 -0
  219. django_cfg/routing/__init__.py +19 -0
  220. django_cfg/routing/callbacks.py +198 -0
  221. django_cfg/routing/routers.py +48 -0
  222. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
  223. django_cfg/templatetags/__init__.py +0 -0
  224. django_cfg/templatetags/django_cfg.py +33 -0
  225. django_cfg/urls.py +33 -0
  226. django_cfg/utils/path_resolution.py +1 -1
  227. django_cfg/utils/smart_defaults.py +7 -61
  228. django_cfg/utils/toolkit.py +663 -0
  229. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/METADATA +83 -86
  230. django_cfg-1.2.1.dist-info/RECORD +441 -0
  231. django_cfg/archive/django_sample.zip +0 -0
  232. django_cfg/models/unfold.py +0 -271
  233. django_cfg/modules/unfold/__init__.py +0 -29
  234. django_cfg/modules/unfold/dashboard.py +0 -318
  235. django_cfg/pyproject.toml +0 -370
  236. django_cfg/routers.py +0 -83
  237. django_cfg-1.1.82.dist-info/RECORD +0 -278
  238. /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
  239. /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
  240. /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
  241. /django_cfg/{version_check.py → utils/version_check.py} +0 -0
  242. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/WHEEL +0 -0
  243. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/entry_points.txt +0 -0
  244. {django_cfg-1.1.82.dist-info → django_cfg-1.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,482 @@
1
+ """
2
+ Toolsets admin interfaces with Unfold optimization.
3
+ """
4
+
5
+ from django.contrib import admin, messages
6
+ from django.utils.html import format_html
7
+ from django.urls import reverse
8
+ from django.utils.safestring import mark_safe
9
+ from django.db import models
10
+ from django.db.models import Count, Avg, Sum, Q
11
+ from django.utils import timezone
12
+ from django.db.models.fields.json import JSONField
13
+ from datetime import timedelta
14
+ from django_json_widget.widgets import JSONEditorWidget
15
+ from unfold.admin import ModelAdmin, TabularInline
16
+ from unfold.decorators import display, action
17
+ from unfold.enums import ActionVariant
18
+ from unfold.contrib.filters.admin import AutocompleteSelectFilter, AutocompleteSelectMultipleFilter
19
+ from unfold.contrib.forms.widgets import WysiwygWidget
20
+
21
+ from ..models.toolsets import ToolExecution, ApprovalLog, ToolsetConfiguration
22
+
23
+
24
+ @admin.register(ToolExecution)
25
+ class ToolExecutionAdmin(ModelAdmin):
26
+ """Admin interface for ToolExecution with Unfold styling."""
27
+
28
+ list_display = [
29
+ 'id_display', 'tool_name_display', 'toolset_badge', 'status_badge', 'user',
30
+ 'execution_metrics', 'retry_badge', 'created_at'
31
+ ]
32
+ ordering = ['-created_at']
33
+ list_filter = [
34
+ 'status', 'tool_name', 'toolset_name', 'created_at',
35
+ ('user', AutocompleteSelectFilter),
36
+ ('agent_execution', AutocompleteSelectFilter)
37
+ ]
38
+ search_fields = ['tool_name', 'toolset_name', 'user__username', 'arguments', 'result']
39
+ autocomplete_fields = ['user', 'agent_execution']
40
+ readonly_fields = [
41
+ 'id', 'execution_time', 'created_at', 'started_at', 'completed_at',
42
+ 'duration_display', 'arguments_preview', 'result_preview', 'error_preview'
43
+ ]
44
+
45
+ # Unfold form field overrides
46
+ formfield_overrides = {
47
+ models.TextField: {"widget": WysiwygWidget},
48
+ JSONField: {"widget": JSONEditorWidget},
49
+ }
50
+
51
+ fieldsets = (
52
+ ("🛠️ Tool Info", {
53
+ 'fields': ('id', 'tool_name', 'toolset_name', 'user', 'status'),
54
+ 'classes': ('tab',)
55
+ }),
56
+ ("📝 Execution Data", {
57
+ 'fields': ('arguments_preview', 'arguments', 'result_preview', 'result', 'error_preview', 'error_message'),
58
+ 'classes': ('tab',)
59
+ }),
60
+ ("📊 Metrics", {
61
+ 'fields': ('execution_time', 'retry_count'),
62
+ 'classes': ('tab',)
63
+ }),
64
+ ("🔗 Context", {
65
+ 'fields': ('agent_execution',),
66
+ 'classes': ('tab', 'collapse')
67
+ }),
68
+ ("⏰ Timestamps", {
69
+ 'fields': ('created_at', 'started_at', 'completed_at', 'duration_display'),
70
+ 'classes': ('tab', 'collapse')
71
+ }),
72
+ )
73
+
74
+ actions = ['retry_failed_tools', 'clear_errors']
75
+
76
+ @display(description="ID")
77
+ def id_display(self, obj):
78
+ """Enhanced ID display."""
79
+ return format_html(
80
+ '<span class="font-mono text-sm text-gray-600">#{}</span>',
81
+ str(obj.id)[:8]
82
+ )
83
+
84
+ @display(description="Tool")
85
+ def tool_name_display(self, obj):
86
+ """Enhanced tool name display."""
87
+ return format_html(
88
+ '<div class="flex items-center space-x-2">'
89
+ '<span class="text-orange-600 font-medium">{}</span>'
90
+ '</div>',
91
+ obj.tool_name
92
+ )
93
+
94
+ @display(description="Toolset")
95
+ def toolset_badge(self, obj):
96
+ """Toolset badge."""
97
+ if not obj.toolset_name:
98
+ return "-"
99
+ return format_html(
100
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-800">{}</span>',
101
+ obj.toolset_name
102
+ )
103
+
104
+ @display(description="Status")
105
+ def status_badge(self, obj):
106
+ """Status badge with color coding."""
107
+ colors = {
108
+ 'pending': 'bg-yellow-100 text-yellow-800',
109
+ 'running': 'bg-blue-100 text-blue-800',
110
+ 'completed': 'bg-green-100 text-green-800',
111
+ 'failed': 'bg-red-100 text-red-800',
112
+ 'cancelled': 'bg-gray-100 text-gray-800'
113
+ }
114
+ color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
115
+ return format_html(
116
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
117
+ color_class, obj.get_status_display()
118
+ )
119
+
120
+ @display(description="Metrics")
121
+ def execution_metrics(self, obj):
122
+ """Combined execution metrics."""
123
+ return format_html(
124
+ '<div class="text-sm space-y-1">'
125
+ '<div><span class="font-medium">Time:</span> {}</div>'
126
+ '</div>',
127
+ f"{obj.execution_time:.3f}s" if obj.execution_time else "-"
128
+ )
129
+
130
+ @display(description="Retries")
131
+ def retry_badge(self, obj):
132
+ """Retry count badge."""
133
+ if obj.retry_count > 0:
134
+ return format_html(
135
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-orange-100 text-orange-800">{}</span>',
136
+ obj.retry_count
137
+ )
138
+ return format_html(
139
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">0</span>'
140
+ )
141
+
142
+ @display(description="Duration")
143
+ def duration_display(self, obj):
144
+ """Display execution duration."""
145
+ if obj.duration:
146
+ return f"{obj.duration:.3f}s"
147
+ return "-"
148
+
149
+ @display(description="Arguments Preview")
150
+ def arguments_preview(self, obj):
151
+ """Preview of arguments."""
152
+ if not obj.arguments:
153
+ return "-"
154
+ preview = str(obj.arguments)[:200] + "..." if len(str(obj.arguments)) > 200 else str(obj.arguments)
155
+ return format_html(
156
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
157
+ preview
158
+ )
159
+
160
+ @display(description="Result Preview")
161
+ def result_preview(self, obj):
162
+ """Preview of result."""
163
+ if not obj.result:
164
+ return "-"
165
+ preview = str(obj.result)[:200] + "..." if len(str(obj.result)) > 200 else str(obj.result)
166
+ return format_html(
167
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
168
+ preview
169
+ )
170
+
171
+ @display(description="Error Preview")
172
+ def error_preview(self, obj):
173
+ """Preview of error message."""
174
+ if not obj.error_message:
175
+ return "-"
176
+ preview = obj.error_message[:200] + "..." if len(obj.error_message) > 200 else obj.error_message
177
+ return format_html(
178
+ '<div class="text-sm text-red-600 max-w-md">{}</div>',
179
+ preview
180
+ )
181
+
182
+ @action(description="Retry failed tools", icon="refresh", variant=ActionVariant.WARNING)
183
+ def retry_failed_tools(self, request, queryset):
184
+ """Retry failed tool executions."""
185
+ failed_count = queryset.filter(status='failed').count()
186
+ messages.warning(request, f"Retry functionality not implemented yet. {failed_count} failed tools selected.")
187
+
188
+ @action(description="Clear errors", icon="clear", variant=ActionVariant.INFO)
189
+ def clear_errors(self, request, queryset):
190
+ """Clear error messages."""
191
+ error_count = queryset.exclude(error_message__isnull=True).exclude(error_message='').count()
192
+ messages.info(request, f"Error clearing not implemented yet. {error_count} tools with errors selected.")
193
+
194
+ def get_queryset(self, request):
195
+ """Optimize queryset."""
196
+ return super().get_queryset(request).select_related('user', 'agent_execution')
197
+
198
+
199
+ @admin.register(ApprovalLog)
200
+ class ApprovalLogAdmin(ModelAdmin):
201
+ """Admin interface for ApprovalLog with Unfold styling."""
202
+
203
+ list_display = [
204
+ 'approval_id_display', 'tool_name_display', 'status_badge', 'user',
205
+ 'decision_info', 'time_metrics', 'expiry_status', 'requested_at'
206
+ ]
207
+ ordering = ['-requested_at']
208
+ list_filter = [
209
+ 'status', 'tool_name', 'requested_at',
210
+ ('user', AutocompleteSelectFilter),
211
+ ('approved_by', AutocompleteSelectFilter),
212
+ ('rejected_by', AutocompleteSelectFilter)
213
+ ]
214
+ search_fields = ['approval_id', 'tool_name', 'user__username', 'justification']
215
+ autocomplete_fields = ['user', 'approved_by', 'rejected_by']
216
+ readonly_fields = [
217
+ 'approval_id', 'requested_at', 'decided_at', 'time_to_decision',
218
+ 'is_expired', 'tool_args_preview', 'justification_preview'
219
+ ]
220
+
221
+ # Unfold form field overrides
222
+ formfield_overrides = {
223
+ models.TextField: {"widget": WysiwygWidget},
224
+ JSONField: {"widget": JSONEditorWidget},
225
+ }
226
+
227
+ fieldsets = (
228
+ ("✅ Approval Info", {
229
+ 'fields': ('approval_id', 'tool_name', 'status', 'user'),
230
+ 'classes': ('tab',)
231
+ }),
232
+ ("📝 Request Details", {
233
+ 'fields': ('tool_args_preview', 'tool_args', 'justification_preview', 'justification'),
234
+ 'classes': ('tab',)
235
+ }),
236
+ ("🎯 Decision", {
237
+ 'fields': ('approved_by', 'rejected_by', 'rejection_reason'),
238
+ 'classes': ('tab',)
239
+ }),
240
+ ("⏰ Timestamps", {
241
+ 'fields': ('requested_at', 'decided_at', 'expires_at', 'time_to_decision', 'is_expired'),
242
+ 'classes': ('tab', 'collapse')
243
+ }),
244
+ )
245
+
246
+ actions = ['approve_selected', 'reject_selected', 'extend_expiry']
247
+
248
+ @display(description="Approval ID")
249
+ def approval_id_display(self, obj):
250
+ """Enhanced approval ID display."""
251
+ return format_html(
252
+ '<span class="font-mono text-sm text-blue-600">#{}</span>',
253
+ obj.approval_id[:8] if obj.approval_id else "N/A"
254
+ )
255
+
256
+ @display(description="Tool")
257
+ def tool_name_display(self, obj):
258
+ """Enhanced tool name display."""
259
+ return format_html(
260
+ '<div class="flex items-center space-x-2">'
261
+ '<span class="text-purple-600 font-medium">{}</span>'
262
+ '</div>',
263
+ obj.tool_name
264
+ )
265
+
266
+ @display(description="Status")
267
+ def status_badge(self, obj):
268
+ """Status badge with color coding."""
269
+ colors = {
270
+ 'pending': 'bg-yellow-100 text-yellow-800',
271
+ 'approved': 'bg-green-100 text-green-800',
272
+ 'rejected': 'bg-red-100 text-red-800',
273
+ 'expired': 'bg-gray-100 text-gray-800'
274
+ }
275
+ color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
276
+ return format_html(
277
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
278
+ color_class, obj.get_status_display()
279
+ )
280
+
281
+ @display(description="Decision")
282
+ def decision_info(self, obj):
283
+ """Decision information."""
284
+ if obj.approved_by:
285
+ return format_html(
286
+ '<div class="text-sm">'
287
+ '<div class="text-green-600 font-medium">✓ Approved by</div>'
288
+ '<div class="text-gray-600">{}</div>'
289
+ '</div>',
290
+ obj.approved_by.username
291
+ )
292
+ elif obj.rejected_by:
293
+ return format_html(
294
+ '<div class="text-sm">'
295
+ '<div class="text-red-600 font-medium">✗ Rejected by</div>'
296
+ '<div class="text-gray-600">{}</div>'
297
+ '</div>',
298
+ obj.rejected_by.username
299
+ )
300
+ return format_html(
301
+ '<div class="text-sm text-yellow-600">⏳ Pending</div>'
302
+ )
303
+
304
+ @display(description="Time Metrics")
305
+ def time_metrics(self, obj):
306
+ """Time-related metrics."""
307
+ decision_time = f"{obj.time_to_decision:.1f}s" if obj.time_to_decision else "N/A"
308
+ return format_html(
309
+ '<div class="text-sm space-y-1">'
310
+ '<div><span class="font-medium">Decision:</span> {}</div>'
311
+ '</div>',
312
+ decision_time
313
+ )
314
+
315
+ @display(description="Expiry", boolean=True)
316
+ def expiry_status(self, obj):
317
+ """Expiry status."""
318
+ return not obj.is_expired
319
+
320
+ @display(description="Tool Args Preview")
321
+ def tool_args_preview(self, obj):
322
+ """Preview of tool arguments."""
323
+ if not obj.tool_args:
324
+ return "-"
325
+ preview = str(obj.tool_args)[:200] + "..." if len(str(obj.tool_args)) > 200 else str(obj.tool_args)
326
+ return format_html(
327
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
328
+ preview
329
+ )
330
+
331
+ @display(description="Justification Preview")
332
+ def justification_preview(self, obj):
333
+ """Preview of justification."""
334
+ if not obj.justification:
335
+ return "-"
336
+ preview = obj.justification[:200] + "..." if len(obj.justification) > 200 else obj.justification
337
+ return format_html(
338
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
339
+ preview
340
+ )
341
+
342
+ @action(description="Approve selected requests", icon="check", variant=ActionVariant.SUCCESS)
343
+ def approve_selected(self, request, queryset):
344
+ """Approve selected requests."""
345
+ count = 0
346
+ for approval in queryset.filter(status='pending'):
347
+ approval.approve(request.user)
348
+ count += 1
349
+
350
+ messages.success(request, f"Approved {count} requests.")
351
+
352
+ @action(description="Reject selected requests", icon="close", variant=ActionVariant.DANGER)
353
+ def reject_selected(self, request, queryset):
354
+ """Reject selected requests."""
355
+ count = 0
356
+ for approval in queryset.filter(status='pending'):
357
+ approval.reject(request.user, "Bulk rejection by admin")
358
+ count += 1
359
+
360
+ messages.warning(request, f"Rejected {count} requests.")
361
+
362
+ @action(description="Extend expiry", icon="schedule", variant=ActionVariant.INFO)
363
+ def extend_expiry(self, request, queryset):
364
+ """Extend expiry time for selected requests."""
365
+ pending_count = queryset.filter(status='pending').count()
366
+ messages.info(request, f"Expiry extension not implemented yet. {pending_count} pending requests selected.")
367
+
368
+ def get_queryset(self, request):
369
+ """Optimize queryset."""
370
+ return super().get_queryset(request).select_related('user', 'approved_by', 'rejected_by')
371
+
372
+
373
+ @admin.register(ToolsetConfiguration)
374
+ class ToolsetConfigurationAdmin(ModelAdmin):
375
+ """Admin interface for ToolsetConfiguration with Unfold styling."""
376
+
377
+ list_display = [
378
+ 'name_display', 'toolset_class_badge', 'status_badge', 'usage_info', 'created_by', 'created_at'
379
+ ]
380
+ ordering = ['-created_at']
381
+ list_filter = [
382
+ 'is_active', 'created_at',
383
+ ('created_by', AutocompleteSelectFilter)
384
+ ]
385
+ search_fields = ['name', 'description', 'toolset_class']
386
+ autocomplete_fields = ['created_by', 'allowed_users', 'allowed_groups']
387
+ readonly_fields = ['created_at', 'updated_at', 'config_preview']
388
+
389
+ # Unfold form field overrides
390
+ formfield_overrides = {
391
+ models.TextField: {"widget": WysiwygWidget},
392
+ JSONField: {"widget": JSONEditorWidget},
393
+ }
394
+
395
+ fieldsets = (
396
+ ("🔧 Basic Information", {
397
+ 'fields': ('name', 'description', 'toolset_class'),
398
+ 'classes': ('tab',)
399
+ }),
400
+ ("⚙️ Configuration", {
401
+ 'fields': ('config_preview', 'config'),
402
+ 'classes': ('tab',)
403
+ }),
404
+ ("🔐 Access Control", {
405
+ 'fields': ('is_active', 'allowed_users', 'allowed_groups'),
406
+ 'classes': ('tab',)
407
+ }),
408
+ ("📝 Metadata", {
409
+ 'fields': ('created_by', 'created_at', 'updated_at'),
410
+ 'classes': ('tab', 'collapse')
411
+ }),
412
+ )
413
+
414
+ actions = ['activate_toolsets', 'deactivate_toolsets']
415
+
416
+ @display(description="Toolset Name")
417
+ def name_display(self, obj):
418
+ """Enhanced name display."""
419
+ return format_html(
420
+ '<div class="flex items-center space-x-2">'
421
+ '<span class="text-teal-600 font-medium">{}</span>'
422
+ '</div>',
423
+ obj.name
424
+ )
425
+
426
+ @display(description="Class")
427
+ def toolset_class_badge(self, obj):
428
+ """Toolset class badge."""
429
+ if not obj.toolset_class:
430
+ return "-"
431
+ class_name = obj.toolset_class.split('.')[-1] if '.' in obj.toolset_class else obj.toolset_class
432
+ return format_html(
433
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-cyan-100 text-cyan-800">{}</span>',
434
+ class_name
435
+ )
436
+
437
+ @display(description="Status", boolean=True)
438
+ def status_badge(self, obj):
439
+ """Status badge."""
440
+ return obj.is_active
441
+
442
+ @display(description="Usage")
443
+ def usage_info(self, obj):
444
+ """Usage information."""
445
+ users_count = obj.allowed_users.count() if obj.allowed_users.exists() else 0
446
+ groups_count = obj.allowed_groups.count() if obj.allowed_groups.exists() else 0
447
+
448
+ return format_html(
449
+ '<div class="text-sm space-y-1">'
450
+ '<div><span class="font-medium">Users:</span> {}</div>'
451
+ '<div><span class="font-medium">Groups:</span> {}</div>'
452
+ '</div>',
453
+ users_count,
454
+ groups_count
455
+ )
456
+
457
+ @display(description="Config Preview")
458
+ def config_preview(self, obj):
459
+ """Preview of configuration."""
460
+ if not obj.config:
461
+ return "-"
462
+ preview = str(obj.config)[:200] + "..." if len(str(obj.config)) > 200 else str(obj.config)
463
+ return format_html(
464
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
465
+ preview
466
+ )
467
+
468
+ @action(description="Activate toolsets", icon="play_arrow", variant=ActionVariant.SUCCESS)
469
+ def activate_toolsets(self, request, queryset):
470
+ """Activate selected toolsets."""
471
+ updated = queryset.update(is_active=True)
472
+ messages.success(request, f"Activated {updated} toolsets.")
473
+
474
+ @action(description="Deactivate toolsets", icon="pause", variant=ActionVariant.WARNING)
475
+ def deactivate_toolsets(self, request, queryset):
476
+ """Deactivate selected toolsets."""
477
+ updated = queryset.update(is_active=False)
478
+ messages.warning(request, f"Deactivated {updated} toolsets.")
479
+
480
+ def get_queryset(self, request):
481
+ """Optimize queryset."""
482
+ return super().get_queryset(request).select_related('created_by').prefetch_related('allowed_users', 'allowed_groups')
@@ -0,0 +1,29 @@
1
+ """
2
+ Django app configuration for Django Agents.
3
+ """
4
+
5
+ from django.apps import AppConfig
6
+
7
+
8
+ class AgentsConfig(AppConfig):
9
+ """Django app configuration for Django Agents."""
10
+
11
+ default_auto_field = 'django.db.models.BigAutoField'
12
+ name = 'django_cfg.apps.agents'
13
+ label = 'django_cfg_agents'
14
+ verbose_name = 'Django Agents'
15
+
16
+ def ready(self):
17
+ """Initialize app when Django starts."""
18
+ # Import signal handlers
19
+ try:
20
+ from . import signals # noqa
21
+ except ImportError:
22
+ pass
23
+
24
+ # Initialize orchestrator registry
25
+ try:
26
+ from .integration.registry import initialize_registry
27
+ initialize_registry()
28
+ except ImportError:
29
+ pass
@@ -0,0 +1,20 @@
1
+ """
2
+ Core components for Django Orchestrator.
3
+ """
4
+
5
+ from .agent import DjangoAgent
6
+ from .orchestrator import SimpleOrchestrator
7
+ from .dependencies import DjangoDeps
8
+ from .models import ExecutionResult, WorkflowConfig
9
+ from .exceptions import OrchestratorError, AgentNotFoundError, ExecutionError
10
+
11
+ __all__ = [
12
+ "DjangoAgent",
13
+ "SimpleOrchestrator",
14
+ "DjangoDeps",
15
+ "ExecutionResult",
16
+ "WorkflowConfig",
17
+ "OrchestratorError",
18
+ "AgentNotFoundError",
19
+ "ExecutionError",
20
+ ]