django-cfg 1.1.82__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.
- django_cfg/__init__.py +20 -448
- django_cfg/apps/accounts/README.md +3 -3
- django_cfg/apps/accounts/admin/__init__.py +0 -2
- django_cfg/apps/accounts/admin/activity.py +2 -9
- django_cfg/apps/accounts/admin/filters.py +0 -42
- django_cfg/apps/accounts/admin/inlines.py +8 -8
- django_cfg/apps/accounts/admin/otp.py +5 -5
- django_cfg/apps/accounts/admin/registration_source.py +1 -8
- django_cfg/apps/accounts/admin/user.py +12 -20
- django_cfg/apps/accounts/managers/user_manager.py +2 -129
- django_cfg/apps/accounts/migrations/0006_remove_twilioresponse_otp_secret_and_more.py +46 -0
- django_cfg/apps/accounts/models.py +3 -123
- django_cfg/apps/accounts/serializers/otp.py +40 -44
- django_cfg/apps/accounts/serializers/profile.py +0 -2
- django_cfg/apps/accounts/services/otp_service.py +98 -186
- django_cfg/apps/accounts/signals.py +25 -15
- django_cfg/apps/accounts/utils/auth_email_service.py +84 -0
- django_cfg/apps/accounts/views/otp.py +35 -36
- django_cfg/apps/agents/README.md +129 -0
- django_cfg/apps/agents/__init__.py +68 -0
- django_cfg/apps/agents/admin/__init__.py +17 -0
- django_cfg/apps/agents/admin/execution_admin.py +460 -0
- django_cfg/apps/agents/admin/registry_admin.py +360 -0
- django_cfg/apps/agents/admin/toolsets_admin.py +482 -0
- django_cfg/apps/agents/apps.py +29 -0
- django_cfg/apps/agents/core/__init__.py +20 -0
- django_cfg/apps/agents/core/agent.py +281 -0
- django_cfg/apps/agents/core/dependencies.py +154 -0
- django_cfg/apps/agents/core/exceptions.py +66 -0
- django_cfg/apps/agents/core/models.py +106 -0
- django_cfg/apps/agents/core/orchestrator.py +391 -0
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/agents/integration/__init__.py +14 -0
- django_cfg/apps/agents/integration/middleware.py +80 -0
- django_cfg/apps/agents/integration/registry.py +345 -0
- django_cfg/apps/agents/integration/signals.py +50 -0
- django_cfg/apps/agents/management/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/__init__.py +3 -0
- django_cfg/apps/agents/management/commands/create_agent.py +365 -0
- django_cfg/apps/agents/management/commands/orchestrator_status.py +191 -0
- django_cfg/apps/agents/managers/__init__.py +23 -0
- django_cfg/apps/agents/managers/execution.py +236 -0
- django_cfg/apps/agents/managers/registry.py +254 -0
- django_cfg/apps/agents/managers/toolsets.py +496 -0
- django_cfg/apps/agents/migrations/0001_initial.py +286 -0
- django_cfg/apps/agents/migrations/__init__.py +5 -0
- django_cfg/apps/agents/models/__init__.py +15 -0
- django_cfg/apps/agents/models/execution.py +215 -0
- django_cfg/apps/agents/models/registry.py +220 -0
- django_cfg/apps/agents/models/toolsets.py +305 -0
- django_cfg/apps/agents/patterns/__init__.py +24 -0
- django_cfg/apps/agents/patterns/content_agents.py +234 -0
- django_cfg/apps/agents/toolsets/__init__.py +15 -0
- django_cfg/apps/agents/toolsets/cache_toolset.py +285 -0
- django_cfg/apps/agents/toolsets/django_toolset.py +220 -0
- django_cfg/apps/agents/toolsets/file_toolset.py +324 -0
- django_cfg/apps/agents/toolsets/orm_toolset.py +319 -0
- django_cfg/apps/agents/urls.py +46 -0
- django_cfg/apps/knowbase/README.md +150 -0
- django_cfg/apps/knowbase/__init__.py +27 -0
- django_cfg/apps/knowbase/admin/__init__.py +23 -0
- django_cfg/apps/knowbase/admin/archive_admin.py +857 -0
- django_cfg/apps/knowbase/admin/chat_admin.py +386 -0
- django_cfg/apps/knowbase/admin/document_admin.py +650 -0
- django_cfg/apps/knowbase/admin/external_data_admin.py +685 -0
- django_cfg/apps/knowbase/apps.py +81 -0
- django_cfg/apps/knowbase/config/README.md +176 -0
- django_cfg/apps/knowbase/config/__init__.py +51 -0
- django_cfg/apps/knowbase/config/constance_fields.py +186 -0
- django_cfg/apps/knowbase/config/constance_settings.py +200 -0
- django_cfg/apps/knowbase/config/settings.py +444 -0
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/management/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/__init__.py +0 -0
- django_cfg/apps/knowbase/management/commands/knowbase_stats.py +158 -0
- django_cfg/apps/knowbase/management/commands/setup_knowbase.py +59 -0
- django_cfg/apps/knowbase/managers/__init__.py +22 -0
- django_cfg/apps/knowbase/managers/archive.py +426 -0
- django_cfg/apps/knowbase/managers/base.py +32 -0
- django_cfg/apps/knowbase/managers/chat.py +141 -0
- django_cfg/apps/knowbase/managers/document.py +203 -0
- django_cfg/apps/knowbase/managers/external_data.py +471 -0
- django_cfg/apps/knowbase/migrations/0001_initial.py +427 -0
- django_cfg/apps/knowbase/migrations/0002_archiveitem_archiveitemchunk_documentarchive_and_more.py +434 -0
- django_cfg/apps/knowbase/migrations/__init__.py +5 -0
- django_cfg/apps/knowbase/mixins/__init__.py +15 -0
- django_cfg/apps/knowbase/mixins/config.py +108 -0
- django_cfg/apps/knowbase/mixins/creator.py +81 -0
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +813 -0
- django_cfg/apps/knowbase/mixins/service.py +362 -0
- django_cfg/apps/knowbase/models/__init__.py +41 -0
- django_cfg/apps/knowbase/models/archive.py +599 -0
- django_cfg/apps/knowbase/models/base.py +58 -0
- django_cfg/apps/knowbase/models/chat.py +157 -0
- django_cfg/apps/knowbase/models/document.py +267 -0
- django_cfg/apps/knowbase/models/external_data.py +376 -0
- django_cfg/apps/knowbase/serializers/__init__.py +68 -0
- django_cfg/apps/knowbase/serializers/archive_serializers.py +386 -0
- django_cfg/apps/knowbase/serializers/chat_serializers.py +137 -0
- django_cfg/apps/knowbase/serializers/document_serializers.py +94 -0
- django_cfg/apps/knowbase/serializers/external_data_serializers.py +256 -0
- django_cfg/apps/knowbase/serializers/public_serializers.py +74 -0
- django_cfg/apps/knowbase/services/__init__.py +40 -0
- django_cfg/apps/knowbase/services/archive/__init__.py +42 -0
- django_cfg/apps/knowbase/services/archive/archive_service.py +541 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +791 -0
- django_cfg/apps/knowbase/services/archive/exceptions.py +52 -0
- django_cfg/apps/knowbase/services/archive/extraction_service.py +508 -0
- django_cfg/apps/knowbase/services/archive/vectorization_service.py +362 -0
- django_cfg/apps/knowbase/services/base.py +53 -0
- django_cfg/apps/knowbase/services/chat_service.py +239 -0
- django_cfg/apps/knowbase/services/document_service.py +144 -0
- django_cfg/apps/knowbase/services/embedding/__init__.py +43 -0
- django_cfg/apps/knowbase/services/embedding/async_processor.py +244 -0
- django_cfg/apps/knowbase/services/embedding/batch_processor.py +250 -0
- django_cfg/apps/knowbase/services/embedding/batch_result.py +61 -0
- django_cfg/apps/knowbase/services/embedding/models.py +229 -0
- django_cfg/apps/knowbase/services/embedding/processors.py +148 -0
- django_cfg/apps/knowbase/services/embedding/utils.py +176 -0
- django_cfg/apps/knowbase/services/prompt_builder.py +191 -0
- django_cfg/apps/knowbase/services/search_service.py +293 -0
- django_cfg/apps/knowbase/signals/__init__.py +21 -0
- django_cfg/apps/knowbase/signals/archive_signals.py +211 -0
- django_cfg/apps/knowbase/signals/chat_signals.py +37 -0
- django_cfg/apps/knowbase/signals/document_signals.py +143 -0
- django_cfg/apps/knowbase/signals/external_data_signals.py +157 -0
- django_cfg/apps/knowbase/tasks/__init__.py +39 -0
- django_cfg/apps/knowbase/tasks/archive_tasks.py +316 -0
- django_cfg/apps/knowbase/tasks/document_processing.py +341 -0
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +341 -0
- django_cfg/apps/knowbase/tasks/maintenance.py +195 -0
- django_cfg/apps/knowbase/urls.py +43 -0
- django_cfg/apps/knowbase/utils/__init__.py +12 -0
- django_cfg/apps/knowbase/utils/chunk_settings.py +261 -0
- django_cfg/apps/knowbase/utils/text_processing.py +375 -0
- django_cfg/apps/knowbase/utils/validation.py +99 -0
- django_cfg/apps/knowbase/views/__init__.py +28 -0
- django_cfg/apps/knowbase/views/archive_views.py +469 -0
- django_cfg/apps/knowbase/views/base.py +49 -0
- django_cfg/apps/knowbase/views/chat_views.py +181 -0
- django_cfg/apps/knowbase/views/document_views.py +183 -0
- django_cfg/apps/knowbase/views/public_views.py +129 -0
- django_cfg/apps/leads/admin.py +70 -0
- django_cfg/apps/newsletter/admin.py +234 -0
- django_cfg/apps/newsletter/admin_filters.py +124 -0
- django_cfg/apps/support/admin.py +196 -0
- django_cfg/apps/support/admin_filters.py +71 -0
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +1 -1
- django_cfg/apps/urls.py +5 -4
- django_cfg/cli/README.md +1 -1
- django_cfg/cli/commands/create_project.py +2 -2
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/config.py +44 -0
- django_cfg/core/config.py +29 -82
- django_cfg/core/environment.py +1 -1
- django_cfg/core/generation.py +19 -107
- django_cfg/{integration.py → core/integration.py} +18 -16
- django_cfg/core/validation.py +1 -1
- django_cfg/management/__init__.py +1 -1
- django_cfg/management/commands/__init__.py +1 -1
- django_cfg/management/commands/auto_generate.py +482 -0
- django_cfg/management/commands/migrator.py +19 -101
- django_cfg/management/commands/test_email.py +1 -1
- django_cfg/middleware/README.md +0 -158
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/api.py +145 -0
- django_cfg/models/base.py +287 -0
- django_cfg/models/cache.py +4 -4
- django_cfg/models/constance.py +25 -88
- django_cfg/models/database.py +9 -9
- django_cfg/models/drf.py +3 -36
- django_cfg/models/email.py +163 -0
- django_cfg/models/environment.py +276 -0
- django_cfg/models/limits.py +1 -1
- django_cfg/models/logging.py +366 -0
- django_cfg/models/revolution.py +41 -2
- django_cfg/models/security.py +125 -0
- django_cfg/models/services.py +1 -1
- django_cfg/modules/__init__.py +2 -56
- django_cfg/modules/base.py +78 -52
- django_cfg/modules/django_currency/service.py +2 -2
- django_cfg/modules/django_email.py +2 -2
- django_cfg/modules/django_health.py +267 -0
- django_cfg/modules/django_llm/llm/client.py +79 -17
- django_cfg/modules/django_llm/translator/translator.py +2 -2
- django_cfg/modules/django_logger.py +2 -2
- django_cfg/modules/django_ngrok.py +2 -2
- django_cfg/modules/django_tasks.py +68 -3
- django_cfg/modules/django_telegram.py +3 -3
- django_cfg/modules/django_twilio/sendgrid_service.py +2 -2
- django_cfg/modules/django_twilio/service.py +2 -2
- django_cfg/modules/django_twilio/simple_service.py +2 -2
- django_cfg/modules/django_twilio/twilio_service.py +2 -2
- django_cfg/modules/django_unfold/__init__.py +69 -0
- django_cfg/modules/{unfold → django_unfold}/callbacks.py +23 -22
- django_cfg/modules/django_unfold/dashboard.py +278 -0
- django_cfg/modules/django_unfold/icons/README.md +145 -0
- django_cfg/modules/django_unfold/icons/__init__.py +12 -0
- django_cfg/modules/django_unfold/icons/constants.py +2851 -0
- django_cfg/modules/django_unfold/icons/generate_icons.py +486 -0
- django_cfg/modules/django_unfold/models/__init__.py +42 -0
- django_cfg/modules/django_unfold/models/config.py +601 -0
- django_cfg/modules/django_unfold/models/dashboard.py +206 -0
- django_cfg/modules/django_unfold/models/dropdown.py +40 -0
- django_cfg/modules/django_unfold/models/navigation.py +73 -0
- django_cfg/modules/django_unfold/models/tabs.py +25 -0
- django_cfg/modules/{unfold → django_unfold}/system_monitor.py +2 -2
- django_cfg/modules/django_unfold/utils.py +140 -0
- django_cfg/registry/__init__.py +23 -0
- django_cfg/registry/core.py +61 -0
- django_cfg/registry/exceptions.py +11 -0
- django_cfg/registry/modules.py +12 -0
- django_cfg/registry/services.py +26 -0
- django_cfg/registry/third_party.py +52 -0
- django_cfg/routing/__init__.py +19 -0
- django_cfg/routing/callbacks.py +198 -0
- django_cfg/routing/routers.py +48 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +8 -9
- django_cfg/templatetags/__init__.py +0 -0
- django_cfg/templatetags/django_cfg.py +33 -0
- django_cfg/urls.py +33 -0
- django_cfg/utils/path_resolution.py +1 -1
- django_cfg/utils/smart_defaults.py +7 -61
- django_cfg/utils/toolkit.py +663 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.0.dist-info}/METADATA +83 -86
- django_cfg-1.2.0.dist-info/RECORD +441 -0
- django_cfg/archive/django_sample.zip +0 -0
- django_cfg/models/unfold.py +0 -271
- django_cfg/modules/unfold/__init__.py +0 -29
- django_cfg/modules/unfold/dashboard.py +0 -318
- django_cfg/pyproject.toml +0 -370
- django_cfg/routers.py +0 -83
- django_cfg-1.1.82.dist-info/RECORD +0 -278
- /django_cfg/{exceptions.py → core/exceptions.py} +0 -0
- /django_cfg/modules/{unfold → django_unfold}/models.py +0 -0
- /django_cfg/modules/{unfold → django_unfold}/tailwind.py +0 -0
- /django_cfg/{version_check.py → utils/version_check.py} +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.82.dist-info → django_cfg-1.2.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.82.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')
|