django-cfg 1.1.81__py3-none-any.whl → 1.2.0__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 (246) 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 +444 -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 +79 -17
  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/templates/guide.md +266 -0
  198. django_cfg/modules/django_twilio/twilio_service.py +2 -2
  199. django_cfg/modules/django_unfold/__init__.py +69 -0
  200. django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
  201. django_cfg/modules/django_unfold/dashboard.py +278 -0
  202. django_cfg/modules/django_unfold/icons/README.md +145 -0
  203. django_cfg/modules/django_unfold/icons/__init__.py +12 -0
  204. django_cfg/modules/django_unfold/icons/constants.py +2851 -0
  205. django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
  206. django_cfg/modules/django_unfold/models/__init__.py +42 -0
  207. django_cfg/modules/django_unfold/models/config.py +601 -0
  208. django_cfg/modules/django_unfold/models/dashboard.py +206 -0
  209. django_cfg/modules/django_unfold/models/dropdown.py +40 -0
  210. django_cfg/modules/django_unfold/models/navigation.py +73 -0
  211. django_cfg/modules/django_unfold/models/tabs.py +25 -0
  212. django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
  213. django_cfg/modules/django_unfold/utils.py +140 -0
  214. django_cfg/registry/__init__.py +23 -0
  215. django_cfg/registry/core.py +61 -0
  216. django_cfg/registry/exceptions.py +11 -0
  217. django_cfg/registry/modules.py +12 -0
  218. django_cfg/registry/services.py +26 -0
  219. django_cfg/registry/third_party.py +52 -0
  220. django_cfg/routing/__init__.py +19 -0
  221. django_cfg/routing/callbacks.py +198 -0
  222. django_cfg/routing/routers.py +48 -0
  223. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
  224. django_cfg/templatetags/__init__.py +0 -0
  225. django_cfg/templatetags/django_cfg.py +33 -0
  226. django_cfg/urls.py +33 -0
  227. django_cfg/utils/path_resolution.py +1 -1
  228. django_cfg/utils/smart_defaults.py +7 -61
  229. django_cfg/utils/toolkit.py +663 -0
  230. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/METADATA +83 -86
  231. django_cfg-1.2.0.dist-info/RECORD +441 -0
  232. django_cfg/apps/tasks/@docs/README.md +0 -195
  233. django_cfg/archive/django_sample.zip +0 -0
  234. django_cfg/models/unfold.py +0 -271
  235. django_cfg/modules/unfold/__init__.py +0 -29
  236. django_cfg/modules/unfold/dashboard.py +0 -318
  237. django_cfg/pyproject.toml +0 -370
  238. django_cfg/routers.py +0 -83
  239. django_cfg-1.1.81.dist-info/RECORD +0 -278
  240. /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
  241. /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
  242. /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
  243. /django_cfg/{version_check.py → utils/version_check.py} +0 -0
  244. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
  246. {django_cfg-1.1.81.dist-info → django_cfg-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,181 @@
1
+ """
2
+ Chat API views for RAG-powered conversations.
3
+ """
4
+
5
+ from rest_framework import status
6
+ from rest_framework.decorators import action
7
+ from rest_framework.response import Response
8
+ from django_ratelimit.decorators import ratelimit
9
+ from django.utils.decorators import method_decorator
10
+ from drf_spectacular.utils import extend_schema, OpenApiExample
11
+
12
+ from ..models import ChatSession
13
+ from ..services import ChatService
14
+ from ..serializers import (
15
+ ChatSessionCreateSerializer,
16
+ ChatSessionSerializer,
17
+ ChatQuerySerializer,
18
+ ChatResponseSerializer,
19
+ ChatHistorySerializer
20
+ )
21
+ from .base import BaseKnowledgeViewSet
22
+
23
+
24
+ class ChatSessionViewSet(BaseKnowledgeViewSet):
25
+ """Chat session management endpoints."""
26
+
27
+ queryset = ChatSession.objects.all()
28
+ serializer_class = ChatSessionSerializer
29
+ service_class = ChatService
30
+
31
+ def get_serializer_class(self):
32
+ """Return appropriate serializer based on action."""
33
+ if self.action == 'create':
34
+ return ChatSessionCreateSerializer
35
+ return ChatSessionSerializer
36
+
37
+ @extend_schema(
38
+ summary="Create new chat session",
39
+ responses={201: ChatSessionSerializer}
40
+ )
41
+ def create(self, request, *args, **kwargs):
42
+ """Create new chat session."""
43
+ serializer = self.get_serializer(data=request.data)
44
+ serializer.is_valid(raise_exception=True)
45
+
46
+ service = self.get_service()
47
+ session = service.create_session(
48
+ title=serializer.validated_data['title'],
49
+ model_name=serializer.validated_data['model_name'],
50
+ temperature=serializer.validated_data['temperature'],
51
+ max_context_chunks=serializer.validated_data['max_context_chunks']
52
+ )
53
+
54
+ response_serializer = ChatSessionSerializer(session)
55
+ return Response(response_serializer.data, status=status.HTTP_201_CREATED)
56
+
57
+ @extend_schema(
58
+ summary="List user chat sessions",
59
+ responses={200: ChatSessionSerializer(many=True)}
60
+ )
61
+ def list(self, request, *args, **kwargs):
62
+ """List user chat sessions with filtering."""
63
+ # Add filtering by is_active
64
+ queryset = self.get_queryset()
65
+
66
+ # Filter by active status if requested
67
+ is_active = request.query_params.get('is_active')
68
+ if is_active is not None:
69
+ is_active_bool = is_active.lower() in ('true', '1', 'yes')
70
+ queryset = queryset.filter(is_active=is_active_bool)
71
+
72
+ # Apply pagination
73
+ page = self.paginate_queryset(queryset)
74
+ if page is not None:
75
+ serializer = self.get_serializer(page, many=True)
76
+ return self.get_paginated_response(serializer.data)
77
+
78
+ serializer = self.get_serializer(queryset, many=True)
79
+ return Response(serializer.data)
80
+
81
+ @extend_schema(
82
+ summary="Archive chat session",
83
+ responses={200: ChatSessionSerializer}
84
+ )
85
+ @action(detail=True, methods=['post'])
86
+ def archive(self, request, pk=None):
87
+ """Archive (deactivate) chat session."""
88
+ session = self.get_object()
89
+ session.archive()
90
+
91
+ serializer = self.get_serializer(session)
92
+ return Response(serializer.data)
93
+
94
+ @extend_schema(
95
+ summary="Activate chat session",
96
+ responses={200: ChatSessionSerializer}
97
+ )
98
+ @action(detail=True, methods=['post'])
99
+ def activate(self, request, pk=None):
100
+ """Activate chat session."""
101
+ session = self.get_object()
102
+ session.activate()
103
+
104
+ serializer = self.get_serializer(session)
105
+ return Response(serializer.data)
106
+
107
+
108
+ class ChatViewSet(BaseKnowledgeViewSet):
109
+ """Chat query endpoints."""
110
+
111
+ service_class = ChatService
112
+ serializer_class = ChatResponseSerializer
113
+
114
+ def get_serializer_class(self):
115
+ """Return appropriate serializer based on action."""
116
+ if self.action == 'query':
117
+ return ChatQuerySerializer
118
+ elif self.action == 'history':
119
+ return ChatHistorySerializer
120
+ return ChatResponseSerializer
121
+
122
+ @extend_schema(
123
+ summary="Process chat query with RAG",
124
+ request=ChatQuerySerializer,
125
+ responses={200: ChatResponseSerializer},
126
+ examples=[
127
+ OpenApiExample(
128
+ "Simple Query",
129
+ value={
130
+ "query": "What is machine learning?",
131
+ "max_tokens": 1000,
132
+ "include_sources": True
133
+ }
134
+ )
135
+ ]
136
+ )
137
+ @method_decorator(ratelimit(key='user', rate='30/m', method='POST'))
138
+ @action(detail=False, methods=['post'])
139
+ def query(self, request):
140
+ """Process chat query with RAG context."""
141
+ serializer = ChatQuerySerializer(data=request.data)
142
+ serializer.is_valid(raise_exception=True)
143
+
144
+ service = self.get_service()
145
+
146
+ # Create session if not provided
147
+ session_id = serializer.validated_data.get('session_id')
148
+ if not session_id:
149
+ session = service.create_session()
150
+ session_id = str(session.id)
151
+ else:
152
+ session_id = str(session_id)
153
+
154
+ # Process query
155
+ result = service.process_query(
156
+ session_id=session_id,
157
+ query=serializer.validated_data['query'],
158
+ max_tokens=serializer.validated_data['max_tokens'],
159
+ include_sources=serializer.validated_data['include_sources']
160
+ )
161
+
162
+ return Response(result)
163
+
164
+ @extend_schema(
165
+ summary="Get chat history",
166
+ responses={200: ChatHistorySerializer}
167
+ )
168
+ @action(detail=True, methods=['get'])
169
+ def history(self, request, pk=None):
170
+ """Get chat session history."""
171
+ service = self.get_service()
172
+ messages = service.get_session_history(session_id=pk)
173
+
174
+ data = {
175
+ 'session_id': pk,
176
+ 'messages': messages,
177
+ 'total_messages': len(messages)
178
+ }
179
+
180
+ serializer = ChatHistorySerializer(data)
181
+ return Response(serializer.data)
@@ -0,0 +1,183 @@
1
+ """
2
+ Document management API views.
3
+ """
4
+
5
+ from rest_framework import status, viewsets
6
+ from rest_framework.decorators import action
7
+ from rest_framework.response import Response
8
+ from rest_framework.permissions import IsAuthenticated, IsAdminUser
9
+ from django_ratelimit.decorators import ratelimit
10
+ from django.utils.decorators import method_decorator
11
+ from django.db import models
12
+ from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter
13
+
14
+ from ..models import Document
15
+ from ..services import DocumentService
16
+ from ..serializers import (
17
+ DocumentCreateSerializer,
18
+ DocumentSerializer,
19
+ DocumentStatsSerializer,
20
+ DocumentProcessingStatusSerializer
21
+ )
22
+ from .base import BaseKnowledgeViewSet
23
+
24
+
25
+ class DocumentViewSet(BaseKnowledgeViewSet):
26
+ """Document management endpoints - Admin only."""
27
+
28
+ queryset = Document.objects.all()
29
+ serializer_class = DocumentSerializer
30
+ service_class = DocumentService
31
+ permission_classes = [IsAuthenticated, IsAdminUser]
32
+
33
+ def get_serializer_class(self):
34
+ """Return appropriate serializer based on action."""
35
+ if self.action == 'create':
36
+ return DocumentCreateSerializer
37
+ elif self.action == 'stats':
38
+ return DocumentStatsSerializer
39
+ elif self.action == 'status':
40
+ return DocumentProcessingStatusSerializer
41
+ return DocumentSerializer
42
+
43
+ @extend_schema(
44
+ summary="Upload new document",
45
+ description="Upload and process a new knowledge document",
46
+ responses={
47
+ 201: DocumentSerializer,
48
+ 400: "Validation errors",
49
+ 413: "File too large",
50
+ 429: "Rate limit exceeded"
51
+ },
52
+ examples=[
53
+ OpenApiExample(
54
+ "Text Document",
55
+ value={
56
+ "title": "API Documentation",
57
+ "content": "# API Guide\n\nThis guide explains...",
58
+ "file_type": "text/markdown"
59
+ }
60
+ )
61
+ ]
62
+ )
63
+ @method_decorator(ratelimit(key='user', rate='10/m', method='POST'))
64
+ def create(self, request, *args, **kwargs):
65
+ """Create new document with async processing."""
66
+ serializer = self.get_serializer(data=request.data)
67
+ serializer.is_valid(raise_exception=True)
68
+
69
+ # Use service layer for business logic
70
+ service = self.get_service()
71
+ document = service.create_document(
72
+ title=serializer.validated_data['title'],
73
+ content=serializer.validated_data['content'],
74
+ file_type=serializer.validated_data['file_type'],
75
+ metadata=serializer.validated_data['metadata']
76
+ )
77
+
78
+ response_serializer = DocumentSerializer(document)
79
+ return Response(response_serializer.data, status=status.HTTP_201_CREATED)
80
+
81
+ @extend_schema(
82
+ summary="List user documents",
83
+ parameters=[
84
+ OpenApiParameter(
85
+ name='status',
86
+ type=str,
87
+ location=OpenApiParameter.QUERY,
88
+ description='Filter by processing status'
89
+ ),
90
+ ],
91
+ responses={200: DocumentSerializer(many=True)}
92
+ )
93
+ def list(self, request, *args, **kwargs):
94
+ """List user documents with filtering and pagination."""
95
+ status_filter = request.query_params.get('status')
96
+
97
+ service = self.get_service()
98
+ queryset = service.get_user_documents(status=status_filter)
99
+
100
+ # Use DRF pagination
101
+ page = self.paginate_queryset(queryset)
102
+ if page is not None:
103
+ serializer = self.get_serializer(page, many=True)
104
+ return self.get_paginated_response(serializer.data)
105
+
106
+ serializer = self.get_serializer(queryset, many=True)
107
+ return Response(serializer.data)
108
+
109
+ @extend_schema(
110
+ summary="Get document details",
111
+ responses={
112
+ 200: DocumentSerializer,
113
+ 404: "Document not found"
114
+ }
115
+ )
116
+ def retrieve(self, request, *args, **kwargs):
117
+ """Get document by ID."""
118
+ return super().retrieve(request, *args, **kwargs)
119
+
120
+ @extend_schema(
121
+ summary="Delete document",
122
+ responses={
123
+ 204: "Document deleted successfully",
124
+ 404: "Document not found"
125
+ }
126
+ )
127
+ def destroy(self, request, *args, **kwargs):
128
+ """Delete document and all associated chunks."""
129
+ return super().destroy(request, *args, **kwargs)
130
+
131
+ @extend_schema(
132
+ summary="Get document processing status",
133
+ responses={200: DocumentProcessingStatusSerializer}
134
+ )
135
+ @action(detail=True, methods=['get'])
136
+ def status(self, request, pk=None):
137
+ """Get document processing status."""
138
+ document = self.get_object()
139
+
140
+ data = {
141
+ 'id': document.id,
142
+ 'status': document.processing_status,
143
+ 'progress': {
144
+ 'chunks_processed': document.chunks_count,
145
+ 'total_tokens': document.total_tokens,
146
+ 'processing_time': document.processing_duration
147
+ },
148
+ 'error': document.processing_error if document.processing_error else None,
149
+ 'processing_time_seconds': document.processing_duration
150
+ }
151
+
152
+ serializer = self.get_serializer(data)
153
+ return Response(serializer.data)
154
+
155
+ @extend_schema(
156
+ summary="Reprocess document",
157
+ description="Trigger reprocessing of document chunks and embeddings"
158
+ )
159
+ @action(detail=True, methods=['post'])
160
+ def reprocess(self, request, pk=None):
161
+ """Trigger document reprocessing."""
162
+ document = self.get_object()
163
+
164
+ service = self.get_service()
165
+ service.reprocess_document(str(document.id))
166
+
167
+ return Response({
168
+ 'message': 'Document reprocessing started',
169
+ 'document_id': str(document.id)
170
+ })
171
+
172
+ @extend_schema(
173
+ summary="Get processing statistics",
174
+ responses={200: DocumentStatsSerializer}
175
+ )
176
+ @action(detail=False, methods=['get'])
177
+ def stats(self, request):
178
+ """Get user's document processing statistics."""
179
+ service = self.get_service()
180
+ stats = service.get_processing_stats()
181
+
182
+ serializer = self.get_serializer(stats)
183
+ return Response(serializer.data)
@@ -0,0 +1,129 @@
1
+ """
2
+ Public API views for client access without authentication.
3
+ """
4
+
5
+ from rest_framework import viewsets
6
+ from rest_framework.response import Response
7
+ from rest_framework.permissions import AllowAny
8
+ from django.db import models
9
+ from drf_spectacular.utils import extend_schema, OpenApiParameter
10
+
11
+ from ..models import Document, DocumentCategory
12
+ from ..serializers.public_serializers import (
13
+ PublicDocumentSerializer,
14
+ PublicDocumentListSerializer,
15
+ PublicCategorySerializer
16
+ )
17
+
18
+
19
+ class PublicDocumentViewSet(viewsets.ReadOnlyModelViewSet):
20
+ """Public document endpoints - read-only access for clients."""
21
+
22
+ serializer_class = PublicDocumentSerializer
23
+ permission_classes = [AllowAny]
24
+ lookup_field = 'pk'
25
+
26
+ def get_queryset(self):
27
+ """Get only publicly accessible documents."""
28
+ return Document.objects.filter(
29
+ processing_status='completed',
30
+ is_public=True
31
+ ).filter(
32
+ models.Q(categories__isnull=True) | models.Q(categories__is_public=True)
33
+ ).prefetch_related('categories')
34
+
35
+ def get_serializer_class(self):
36
+ """Return appropriate serializer based on action."""
37
+ if self.action == 'list':
38
+ return PublicDocumentListSerializer
39
+ return PublicDocumentSerializer
40
+
41
+ @extend_schema(
42
+ summary="List public documents",
43
+ description="Get list of all completed and publicly accessible documents",
44
+ parameters=[
45
+ OpenApiParameter(
46
+ name='search',
47
+ type=str,
48
+ location=OpenApiParameter.QUERY,
49
+ description='Search in title and content'
50
+ ),
51
+ OpenApiParameter(
52
+ name='category',
53
+ type=str,
54
+ location=OpenApiParameter.QUERY,
55
+ description='Filter by category name'
56
+ ),
57
+ ],
58
+ responses={200: PublicDocumentListSerializer(many=True)}
59
+ )
60
+ def list(self, request, *args, **kwargs):
61
+ """List all publicly accessible documents with optional filtering."""
62
+ queryset = self.get_queryset()
63
+
64
+ # Search functionality
65
+ search = request.query_params.get('search')
66
+ if search:
67
+ queryset = queryset.filter(
68
+ models.Q(title__icontains=search) |
69
+ models.Q(content__icontains=search)
70
+ )
71
+
72
+ # Category filter
73
+ category = request.query_params.get('category')
74
+ if category:
75
+ queryset = queryset.filter(categories__name__iexact=category)
76
+
77
+ # Order by creation date (newest first)
78
+ queryset = queryset.order_by('-created_at')
79
+
80
+ # Use DRF pagination
81
+ page = self.paginate_queryset(queryset)
82
+ if page is not None:
83
+ serializer = self.get_serializer(page, many=True)
84
+ return self.get_paginated_response(serializer.data)
85
+
86
+ serializer = self.get_serializer(queryset, many=True)
87
+ return Response(serializer.data)
88
+
89
+ @extend_schema(
90
+ summary="Get public document details",
91
+ description="Get document details by ID (public access)",
92
+ responses={
93
+ 200: PublicDocumentSerializer,
94
+ 404: "Document not found"
95
+ }
96
+ )
97
+ def retrieve(self, request, *args, **kwargs):
98
+ """Get document by ID - public access."""
99
+ return super().retrieve(request, *args, **kwargs)
100
+
101
+
102
+ class PublicCategoryViewSet(viewsets.ReadOnlyModelViewSet):
103
+ """Public category endpoints - read-only access for clients."""
104
+
105
+ queryset = DocumentCategory.objects.filter(is_public=True)
106
+ serializer_class = PublicCategorySerializer
107
+ permission_classes = [AllowAny]
108
+ lookup_field = 'pk'
109
+
110
+ @extend_schema(
111
+ summary="List public categories",
112
+ description="Get list of all public categories",
113
+ responses={200: PublicCategorySerializer(many=True)}
114
+ )
115
+ def list(self, request, *args, **kwargs):
116
+ """List all public categories."""
117
+ return super().list(request, *args, **kwargs)
118
+
119
+ @extend_schema(
120
+ summary="Get public category details",
121
+ description="Get category details by ID (public access)",
122
+ responses={
123
+ 200: PublicCategorySerializer,
124
+ 404: "Category not found"
125
+ }
126
+ )
127
+ def retrieve(self, request, *args, **kwargs):
128
+ """Get category by ID - public access."""
129
+ return super().retrieve(request, *args, **kwargs)
@@ -0,0 +1,70 @@
1
+ from django.contrib import admin, messages
2
+ from django.urls import reverse
3
+ from django.utils.html import format_html
4
+ from django.http import HttpResponseRedirect
5
+ from unfold.admin import ModelAdmin
6
+ from unfold.decorators import action
7
+ from .models import Lead
8
+
9
+
10
+ @admin.register(Lead)
11
+ class LeadAdmin(ModelAdmin):
12
+ list_display = [
13
+ 'name', 'email', 'company', 'contact_type', 'contact_value',
14
+ 'subject', 'status_display', 'created_at'
15
+ ]
16
+ list_display_links = ['name', 'email']
17
+ list_filter = [
18
+ 'status', 'contact_type', 'company', 'created_at'
19
+ ]
20
+ search_fields = [
21
+ 'name', 'email', 'company', 'company_site',
22
+ 'message', 'subject', 'admin_notes'
23
+ ]
24
+ readonly_fields = [
25
+ 'created_at', 'updated_at', 'ip_address', 'user_agent'
26
+ ]
27
+
28
+ fieldsets = (
29
+ ('Basic Information', {
30
+ 'fields': ('name', 'email', 'company', 'company_site')
31
+ }),
32
+ ('Contact Information', {
33
+ 'fields': ('contact_type', 'contact_value')
34
+ }),
35
+ ('Message', {
36
+ 'fields': ('subject', 'message', 'extra')
37
+ }),
38
+ ('Metadata', {
39
+ 'fields': ('site_url', 'ip_address', 'user_agent'),
40
+ 'classes': ('collapse',)
41
+ }),
42
+ ('Status and Processing', {
43
+ 'fields': ('status', 'user', 'admin_notes')
44
+ }),
45
+ ('Timestamps', {
46
+ 'fields': ('created_at', 'updated_at'),
47
+ 'classes': ('collapse',)
48
+ }),
49
+ )
50
+
51
+ def status_display(self, obj):
52
+ status_colors = {
53
+ 'new': '#17a2b8',
54
+ 'contacted': '#ffc107',
55
+ 'qualified': '#28a745',
56
+ 'converted': '#007bff',
57
+ 'rejected': '#dc3545'
58
+ }
59
+ color = status_colors.get(obj.status, '#6c757d')
60
+ return format_html(
61
+ '<span style="background: {}; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">{}</span>',
62
+ color, obj.get_status_display()
63
+ )
64
+ status_display.short_description = 'Status'
65
+
66
+ list_per_page = 50
67
+ date_hierarchy = 'created_at'
68
+
69
+ def get_queryset(self, request):
70
+ return super().get_queryset(request).select_related('user')