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,460 @@
1
+ """
2
+ Execution 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.execution import AgentExecution, WorkflowExecution
22
+
23
+
24
+ class AgentExecutionInlineForWorkflow(TabularInline):
25
+ """Inline for agent executions within workflow with Unfold styling."""
26
+
27
+ model = AgentExecution
28
+ verbose_name = "Agent Execution"
29
+ verbose_name_plural = "🔗 Workflow Steps (Read-only)"
30
+ extra = 0
31
+ max_num = 0
32
+ can_delete = False
33
+ show_change_link = True
34
+
35
+ def has_add_permission(self, request, obj=None):
36
+ return False
37
+
38
+ def has_change_permission(self, request, obj=None):
39
+ return False
40
+
41
+ def has_delete_permission(self, request, obj=None):
42
+ return False
43
+
44
+ fields = [
45
+ 'execution_order', 'agent_name', 'status_badge_inline',
46
+ 'execution_time_display', 'tokens_used', 'cost_display_inline'
47
+ ]
48
+ readonly_fields = [
49
+ 'execution_order', 'agent_name', 'status_badge_inline',
50
+ 'execution_time_display', 'tokens_used', 'cost_display_inline'
51
+ ]
52
+
53
+ # Unfold specific options
54
+ hide_title = False
55
+ classes = ['collapse']
56
+
57
+ @display(description="Status")
58
+ def status_badge_inline(self, obj):
59
+ """Status badge for inline display."""
60
+ colors = {
61
+ 'pending': 'bg-yellow-100 text-yellow-800',
62
+ 'running': 'bg-blue-100 text-blue-800',
63
+ 'completed': 'bg-green-100 text-green-800',
64
+ 'failed': 'bg-red-100 text-red-800',
65
+ 'cancelled': 'bg-gray-100 text-gray-800'
66
+ }
67
+ color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
68
+ return format_html(
69
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
70
+ color_class, obj.get_status_display()
71
+ )
72
+
73
+ @display(description="Execution Time")
74
+ def execution_time_display(self, obj):
75
+ """Execution time display for inline."""
76
+ if obj.execution_time:
77
+ return f"{obj.execution_time:.2f}s"
78
+ return "-"
79
+
80
+ @display(description="Cost")
81
+ def cost_display_inline(self, obj):
82
+ """Cost display for inline."""
83
+ if obj.cost:
84
+ return f"${obj.cost:.4f}"
85
+ return "-"
86
+
87
+ def get_queryset(self, request):
88
+ """Optimize queryset for inline display."""
89
+ return super().get_queryset(request).select_related('user').order_by('execution_order')
90
+
91
+
92
+ @admin.register(AgentExecution)
93
+ class AgentExecutionAdmin(ModelAdmin):
94
+ """Admin interface for AgentExecution with Unfold styling."""
95
+
96
+ list_display = [
97
+ 'id_display', 'agent_name_display', 'status_badge', 'user',
98
+ 'execution_metrics', 'cost_display', 'cached_badge', 'created_at'
99
+ ]
100
+ ordering = ['-created_at']
101
+ list_filter = [
102
+ 'status', 'cached', 'agent_name', 'created_at',
103
+ ('user', AutocompleteSelectFilter),
104
+ ('workflow_execution', AutocompleteSelectFilter)
105
+ ]
106
+ search_fields = ['agent_name', 'user__username', 'input_prompt', 'output_data']
107
+ autocomplete_fields = ['user', 'workflow_execution']
108
+ readonly_fields = [
109
+ 'id', 'execution_time', 'tokens_used', 'cost', 'cached',
110
+ 'created_at', 'started_at', 'completed_at', 'duration_display',
111
+ 'input_preview', 'output_preview', 'error_preview'
112
+ ]
113
+
114
+ # Unfold form field overrides
115
+ formfield_overrides = {
116
+ models.TextField: {"widget": WysiwygWidget},
117
+ JSONField: {"widget": JSONEditorWidget},
118
+ }
119
+
120
+ fieldsets = (
121
+ ("🚀 Execution Info", {
122
+ 'fields': ('id', 'agent_name', 'user', 'status'),
123
+ 'classes': ('tab',)
124
+ }),
125
+ ("📝 Input/Output", {
126
+ 'fields': ('input_preview', 'input_prompt', 'output_preview', 'output_data', 'error_preview', 'error_message'),
127
+ 'classes': ('tab',)
128
+ }),
129
+ ("📊 Metrics", {
130
+ 'fields': ('execution_time', 'tokens_used', 'cost', 'cached'),
131
+ 'classes': ('tab',)
132
+ }),
133
+ ("🔗 Workflow Context", {
134
+ 'fields': ('workflow_execution', 'execution_order'),
135
+ 'classes': ('tab', 'collapse')
136
+ }),
137
+ ("⏰ Timestamps", {
138
+ 'fields': ('created_at', 'started_at', 'completed_at', 'duration_display'),
139
+ 'classes': ('tab', 'collapse')
140
+ }),
141
+ )
142
+
143
+ actions = ['retry_failed_executions', 'clear_cache']
144
+
145
+ @display(description="ID")
146
+ def id_display(self, obj):
147
+ """Enhanced ID display."""
148
+ return format_html(
149
+ '<span class="font-mono text-sm text-gray-600">#{}</span>',
150
+ str(obj.id)[:8]
151
+ )
152
+
153
+ @display(description="Agent")
154
+ def agent_name_display(self, obj):
155
+ """Enhanced agent name display."""
156
+ return format_html(
157
+ '<div class="flex items-center space-x-2">'
158
+ '<span class="text-blue-600 font-medium">{}</span>'
159
+ '</div>',
160
+ obj.agent_name
161
+ )
162
+
163
+ @display(description="Status")
164
+ def status_badge(self, obj):
165
+ """Status badge with color coding."""
166
+ colors = {
167
+ 'pending': 'bg-yellow-100 text-yellow-800',
168
+ 'running': 'bg-blue-100 text-blue-800',
169
+ 'completed': 'bg-green-100 text-green-800',
170
+ 'failed': 'bg-red-100 text-red-800',
171
+ 'cancelled': 'bg-gray-100 text-gray-800'
172
+ }
173
+ color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
174
+ return format_html(
175
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
176
+ color_class, obj.get_status_display()
177
+ )
178
+
179
+ @display(description="Metrics")
180
+ def execution_metrics(self, obj):
181
+ """Combined execution metrics."""
182
+ return format_html(
183
+ '<div class="text-sm space-y-1">'
184
+ '<div><span class="font-medium">Time:</span> {}</div>'
185
+ '<div><span class="font-medium">Tokens:</span> {}</div>'
186
+ '</div>',
187
+ f"{obj.execution_time:.2f}s" if obj.execution_time else "-",
188
+ f"{obj.tokens_used:,}" if obj.tokens_used else "-"
189
+ )
190
+
191
+ @display(description="Cost")
192
+ def cost_display(self, obj):
193
+ """Cost display with formatting."""
194
+ if obj.cost:
195
+ return format_html(
196
+ '<span class="font-mono text-green-600">${:.4f}</span>',
197
+ obj.cost
198
+ )
199
+ return "-"
200
+
201
+ @display(description="Cached", boolean=True)
202
+ def cached_badge(self, obj):
203
+ """Cached status badge."""
204
+ return obj.cached
205
+
206
+ @display(description="Duration")
207
+ def duration_display(self, obj):
208
+ """Display execution duration."""
209
+ if obj.duration:
210
+ return f"{obj.duration:.2f}s"
211
+ return "-"
212
+
213
+ @display(description="Input Preview")
214
+ def input_preview(self, obj):
215
+ """Preview of input prompt."""
216
+ if not obj.input_prompt:
217
+ return "-"
218
+ preview = obj.input_prompt[:200] + "..." if len(obj.input_prompt) > 200 else obj.input_prompt
219
+ return format_html(
220
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
221
+ preview
222
+ )
223
+
224
+ @display(description="Output Preview")
225
+ def output_preview(self, obj):
226
+ """Preview of output data."""
227
+ if not obj.output_data:
228
+ return "-"
229
+ preview = str(obj.output_data)[:200] + "..." if len(str(obj.output_data)) > 200 else str(obj.output_data)
230
+ return format_html(
231
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
232
+ preview
233
+ )
234
+
235
+ @display(description="Error Preview")
236
+ def error_preview(self, obj):
237
+ """Preview of error message."""
238
+ if not obj.error_message:
239
+ return "-"
240
+ preview = obj.error_message[:200] + "..." if len(obj.error_message) > 200 else obj.error_message
241
+ return format_html(
242
+ '<div class="text-sm text-red-600 max-w-md">{}</div>',
243
+ preview
244
+ )
245
+
246
+ @action(description="Retry failed executions", icon="refresh", variant=ActionVariant.WARNING)
247
+ def retry_failed_executions(self, request, queryset):
248
+ """Retry failed executions."""
249
+ failed_count = queryset.filter(status='failed').count()
250
+ messages.warning(request, f"Retry functionality not implemented yet. {failed_count} failed executions selected.")
251
+
252
+ @action(description="Clear cache", icon="clear", variant=ActionVariant.INFO)
253
+ def clear_cache(self, request, queryset):
254
+ """Clear cache for selected executions."""
255
+ cached_count = queryset.filter(cached=True).count()
256
+ messages.info(request, f"Cache clearing not implemented yet. {cached_count} cached executions selected.")
257
+
258
+ def get_queryset(self, request):
259
+ """Optimize queryset."""
260
+ return super().get_queryset(request).select_related('user', 'workflow_execution')
261
+
262
+
263
+ @admin.register(WorkflowExecution)
264
+ class WorkflowExecutionAdmin(ModelAdmin):
265
+ """Admin interface for WorkflowExecution with Unfold styling."""
266
+
267
+ list_display = [
268
+ 'id_display', 'name_display', 'pattern_badge', 'status_badge', 'user',
269
+ 'progress_display', 'metrics_display', 'cost_display', 'created_at'
270
+ ]
271
+ ordering = ['-created_at']
272
+ inlines = [AgentExecutionInlineForWorkflow]
273
+ list_filter = [
274
+ 'status', 'pattern', 'created_at',
275
+ ('user', AutocompleteSelectFilter)
276
+ ]
277
+ search_fields = ['name', 'user__username', 'input_prompt', 'final_output']
278
+ autocomplete_fields = ['user']
279
+ readonly_fields = [
280
+ 'id', 'total_execution_time', 'total_tokens_used', 'total_cost',
281
+ 'created_at', 'started_at', 'completed_at', 'duration_display',
282
+ 'progress_percentage', 'input_preview', 'output_preview', 'error_preview'
283
+ ]
284
+
285
+ # Unfold form field overrides
286
+ formfield_overrides = {
287
+ models.TextField: {"widget": WysiwygWidget},
288
+ JSONField: {"widget": JSONEditorWidget},
289
+ }
290
+
291
+ fieldsets = (
292
+ ("🔄 Workflow Info", {
293
+ 'fields': ('id', 'name', 'user', 'pattern', 'status'),
294
+ 'classes': ('tab',)
295
+ }),
296
+ ("⚙️ Configuration", {
297
+ 'fields': ('agent_names', 'input_preview', 'input_prompt', 'config'),
298
+ 'classes': ('tab',)
299
+ }),
300
+ ("📈 Progress", {
301
+ 'fields': ('current_step', 'total_steps', 'progress_percentage'),
302
+ 'classes': ('tab',)
303
+ }),
304
+ ("📋 Results", {
305
+ 'fields': ('output_preview', 'final_output', 'error_preview', 'error_message'),
306
+ 'classes': ('tab',)
307
+ }),
308
+ ("📊 Metrics", {
309
+ 'fields': ('total_execution_time', 'total_tokens_used', 'total_cost'),
310
+ 'classes': ('tab',)
311
+ }),
312
+ ("⏰ Timestamps", {
313
+ 'fields': ('created_at', 'started_at', 'completed_at', 'duration_display'),
314
+ 'classes': ('tab', 'collapse')
315
+ }),
316
+ )
317
+
318
+ actions = ['cancel_running_workflows', 'retry_failed_workflows']
319
+
320
+ @display(description="ID")
321
+ def id_display(self, obj):
322
+ """Enhanced ID display."""
323
+ return format_html(
324
+ '<span class="font-mono text-sm text-gray-600">#{}</span>',
325
+ str(obj.id)[:8]
326
+ )
327
+
328
+ @display(description="Workflow")
329
+ def name_display(self, obj):
330
+ """Enhanced workflow name display."""
331
+ return format_html(
332
+ '<div class="flex items-center space-x-2">'
333
+ '<span class="text-indigo-600 font-medium">{}</span>'
334
+ '</div>',
335
+ obj.name
336
+ )
337
+
338
+ @display(description="Pattern")
339
+ def pattern_badge(self, obj):
340
+ """Pattern badge."""
341
+ colors = {
342
+ 'sequential': 'bg-blue-100 text-blue-800',
343
+ 'parallel': 'bg-green-100 text-green-800',
344
+ 'conditional': 'bg-purple-100 text-purple-800',
345
+ 'loop': 'bg-orange-100 text-orange-800'
346
+ }
347
+ color_class = colors.get(obj.pattern, 'bg-gray-100 text-gray-800')
348
+ return format_html(
349
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
350
+ color_class, obj.pattern.title() if obj.pattern else 'Unknown'
351
+ )
352
+
353
+ @display(description="Status")
354
+ def status_badge(self, obj):
355
+ """Status badge with color coding."""
356
+ colors = {
357
+ 'pending': 'bg-yellow-100 text-yellow-800',
358
+ 'running': 'bg-blue-100 text-blue-800',
359
+ 'completed': 'bg-green-100 text-green-800',
360
+ 'failed': 'bg-red-100 text-red-800',
361
+ 'cancelled': 'bg-gray-100 text-gray-800'
362
+ }
363
+ color_class = colors.get(obj.status, 'bg-gray-100 text-gray-800')
364
+ return format_html(
365
+ '<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium {}">{}</span>',
366
+ color_class, obj.get_status_display()
367
+ )
368
+
369
+ @display(description="Progress")
370
+ def progress_display(self, obj):
371
+ """Display progress as a progress bar."""
372
+ percentage = obj.progress_percentage
373
+ color_class = 'bg-green-500' if obj.status == 'completed' else 'bg-blue-500' if obj.status == 'running' else 'bg-red-500'
374
+
375
+ return format_html(
376
+ '<div class="w-24 bg-gray-200 rounded-full h-2">'
377
+ '<div class="h-2 rounded-full {} flex items-center justify-center text-xs text-white" style="width: {}%;">'
378
+ '</div>'
379
+ '</div>'
380
+ '<div class="text-xs text-gray-600 mt-1">{}%</div>',
381
+ color_class, percentage, int(percentage)
382
+ )
383
+
384
+ @display(description="Metrics")
385
+ def metrics_display(self, obj):
386
+ """Combined metrics display."""
387
+ return format_html(
388
+ '<div class="text-sm space-y-1">'
389
+ '<div><span class="font-medium">Time:</span> {}</div>'
390
+ '<div><span class="font-medium">Tokens:</span> {}</div>'
391
+ '</div>',
392
+ f"{obj.total_execution_time:.2f}s" if obj.total_execution_time else "-",
393
+ f"{obj.total_tokens_used:,}" if obj.total_tokens_used else "-"
394
+ )
395
+
396
+ @display(description="Cost")
397
+ def cost_display(self, obj):
398
+ """Cost display with formatting."""
399
+ if obj.total_cost:
400
+ return format_html(
401
+ '<span class="font-mono text-green-600">${:.4f}</span>',
402
+ obj.total_cost
403
+ )
404
+ return "-"
405
+
406
+ @display(description="Duration")
407
+ def duration_display(self, obj):
408
+ """Display workflow duration."""
409
+ if obj.duration:
410
+ return f"{obj.duration:.2f}s"
411
+ return "-"
412
+
413
+ @display(description="Input Preview")
414
+ def input_preview(self, obj):
415
+ """Preview of input prompt."""
416
+ if not obj.input_prompt:
417
+ return "-"
418
+ preview = obj.input_prompt[:200] + "..." if len(obj.input_prompt) > 200 else obj.input_prompt
419
+ return format_html(
420
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
421
+ preview
422
+ )
423
+
424
+ @display(description="Output Preview")
425
+ def output_preview(self, obj):
426
+ """Preview of final output."""
427
+ if not obj.final_output:
428
+ return "-"
429
+ preview = str(obj.final_output)[:200] + "..." if len(str(obj.final_output)) > 200 else str(obj.final_output)
430
+ return format_html(
431
+ '<div class="text-sm text-gray-600 max-w-md">{}</div>',
432
+ preview
433
+ )
434
+
435
+ @display(description="Error Preview")
436
+ def error_preview(self, obj):
437
+ """Preview of error message."""
438
+ if not obj.error_message:
439
+ return "-"
440
+ preview = obj.error_message[:200] + "..." if len(obj.error_message) > 200 else obj.error_message
441
+ return format_html(
442
+ '<div class="text-sm text-red-600 max-w-md">{}</div>',
443
+ preview
444
+ )
445
+
446
+ @action(description="Cancel running workflows", icon="stop", variant=ActionVariant.DANGER)
447
+ def cancel_running_workflows(self, request, queryset):
448
+ """Cancel running workflows."""
449
+ running_count = queryset.filter(status='running').count()
450
+ messages.warning(request, f"Cancel functionality not implemented yet. {running_count} running workflows selected.")
451
+
452
+ @action(description="Retry failed workflows", icon="refresh", variant=ActionVariant.WARNING)
453
+ def retry_failed_workflows(self, request, queryset):
454
+ """Retry failed workflows."""
455
+ failed_count = queryset.filter(status='failed').count()
456
+ messages.warning(request, f"Retry functionality not implemented yet. {failed_count} failed workflows selected.")
457
+
458
+ def get_queryset(self, request):
459
+ """Optimize queryset."""
460
+ return super().get_queryset(request).select_related('user')