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,32 @@
|
|
1
|
+
"""
|
2
|
+
Base managers for knowbase models.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.db import models
|
6
|
+
from django.contrib.auth import get_user_model
|
7
|
+
|
8
|
+
User = get_user_model()
|
9
|
+
|
10
|
+
|
11
|
+
class BaseKnowbaseManager(models.Manager):
|
12
|
+
"""Base manager with common functionality for knowbase models."""
|
13
|
+
|
14
|
+
def for_user(self, user):
|
15
|
+
"""Explicitly filter by specific user."""
|
16
|
+
return self.get_queryset().filter(user=user)
|
17
|
+
|
18
|
+
def all_users(self):
|
19
|
+
"""Get unfiltered queryset (admin use)."""
|
20
|
+
return self.get_queryset()
|
21
|
+
|
22
|
+
def public(self):
|
23
|
+
"""Get public records (if model has is_public field)."""
|
24
|
+
if hasattr(self.model, 'is_public'):
|
25
|
+
return self.get_queryset().filter(is_public=True)
|
26
|
+
return self.get_queryset()
|
27
|
+
|
28
|
+
def active(self):
|
29
|
+
"""Get active records (if model has is_active field)."""
|
30
|
+
if hasattr(self.model, 'is_active'):
|
31
|
+
return self.get_queryset().filter(is_active=True)
|
32
|
+
return self.get_queryset()
|
@@ -0,0 +1,141 @@
|
|
1
|
+
"""
|
2
|
+
Chat managers for session and message management.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from django.db import models
|
6
|
+
from django.db.models import Count, Sum, Avg, Q
|
7
|
+
from typing import Optional, List
|
8
|
+
from django.contrib.auth import get_user_model
|
9
|
+
|
10
|
+
User = get_user_model()
|
11
|
+
|
12
|
+
|
13
|
+
class ChatSessionManager(models.Manager):
|
14
|
+
"""Custom manager for ChatSession model."""
|
15
|
+
|
16
|
+
def for_user(self, user):
|
17
|
+
"""Explicitly filter by specific user."""
|
18
|
+
return self.get_queryset().filter(user=user)
|
19
|
+
|
20
|
+
def all_users(self):
|
21
|
+
"""Get unfiltered queryset (admin use)."""
|
22
|
+
return self.get_queryset()
|
23
|
+
|
24
|
+
def active(self):
|
25
|
+
"""Get only active chat sessions."""
|
26
|
+
return self.get_queryset().filter(is_active=True)
|
27
|
+
|
28
|
+
def inactive(self):
|
29
|
+
"""Get only inactive chat sessions."""
|
30
|
+
return self.get_queryset().filter(is_active=False)
|
31
|
+
|
32
|
+
def recent(self, limit: int = 10):
|
33
|
+
"""Get recent sessions ordered by update time."""
|
34
|
+
return self.get_queryset().order_by('-updated_at')[:limit]
|
35
|
+
|
36
|
+
def by_model(self, model_name: str):
|
37
|
+
"""Get sessions using specific model."""
|
38
|
+
return self.get_queryset().filter(model_name=model_name)
|
39
|
+
|
40
|
+
def with_message_count(self):
|
41
|
+
"""Get sessions with message count annotation."""
|
42
|
+
return self.get_queryset().annotate(
|
43
|
+
message_count=Count('messages')
|
44
|
+
)
|
45
|
+
|
46
|
+
def with_stats(self):
|
47
|
+
"""Get sessions with full statistics."""
|
48
|
+
return self.get_queryset().select_related('user').prefetch_related('messages')
|
49
|
+
|
50
|
+
def search_by_title(self, query: str):
|
51
|
+
"""Search sessions by title."""
|
52
|
+
return self.get_queryset().filter(
|
53
|
+
title__icontains=query
|
54
|
+
).order_by('-updated_at')
|
55
|
+
|
56
|
+
def get_user_statistics(self, user=None):
|
57
|
+
"""Get user's chat statistics."""
|
58
|
+
queryset = self.for_user(user) if user else self.get_queryset()
|
59
|
+
|
60
|
+
return queryset.aggregate(
|
61
|
+
total_sessions=Count('id'),
|
62
|
+
active_sessions=Count('id', filter=Q(is_active=True)),
|
63
|
+
total_messages=Sum('messages_count'),
|
64
|
+
total_tokens=Sum('total_tokens_used'),
|
65
|
+
total_cost=Sum('total_cost_usd'),
|
66
|
+
avg_messages_per_session=Avg('messages_count'),
|
67
|
+
avg_cost_per_session=Avg('total_cost_usd')
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
class ChatMessageManager(models.Manager):
|
72
|
+
"""Custom manager for ChatMessage model."""
|
73
|
+
|
74
|
+
def for_user(self, user):
|
75
|
+
"""Explicitly filter by specific user."""
|
76
|
+
return self.get_queryset().filter(user=user)
|
77
|
+
|
78
|
+
def all_users(self):
|
79
|
+
"""Get unfiltered queryset (admin use)."""
|
80
|
+
return self.get_queryset()
|
81
|
+
|
82
|
+
def for_session(self, session_id: str):
|
83
|
+
"""Get messages for specific session."""
|
84
|
+
return self.get_queryset().filter(session_id=session_id).order_by('created_at')
|
85
|
+
|
86
|
+
def user_messages(self):
|
87
|
+
"""Get only user messages."""
|
88
|
+
return self.get_queryset().filter(role='user')
|
89
|
+
|
90
|
+
def assistant_messages(self):
|
91
|
+
"""Get only assistant messages."""
|
92
|
+
return self.get_queryset().filter(role='assistant')
|
93
|
+
|
94
|
+
def system_messages(self):
|
95
|
+
"""Get only system messages."""
|
96
|
+
return self.get_queryset().filter(role='system')
|
97
|
+
|
98
|
+
def by_model(self, model_name: str):
|
99
|
+
"""Get messages generated by specific model."""
|
100
|
+
return self.get_queryset().filter(model_name=model_name)
|
101
|
+
|
102
|
+
def recent(self, limit: int = 50):
|
103
|
+
"""Get recent messages."""
|
104
|
+
return self.get_queryset().order_by('-created_at')[:limit]
|
105
|
+
|
106
|
+
def with_context(self):
|
107
|
+
"""Get messages that have context chunks."""
|
108
|
+
return self.get_queryset().exclude(context_chunks=[])
|
109
|
+
|
110
|
+
def expensive_messages(self, min_cost: float = 0.01):
|
111
|
+
"""Get messages above certain cost threshold."""
|
112
|
+
return self.get_queryset().filter(cost_usd__gte=min_cost)
|
113
|
+
|
114
|
+
def slow_messages(self, min_time_ms: int = 5000):
|
115
|
+
"""Get messages that took long to process."""
|
116
|
+
return self.get_queryset().filter(processing_time_ms__gte=min_time_ms)
|
117
|
+
|
118
|
+
def get_conversation_history(self, session_id: str, limit: Optional[int] = None):
|
119
|
+
"""Get conversation history for session."""
|
120
|
+
queryset = self.for_session(session_id)
|
121
|
+
if limit:
|
122
|
+
# Get last N messages
|
123
|
+
queryset = queryset.order_by('-created_at')[:limit]
|
124
|
+
# Reverse to get chronological order
|
125
|
+
queryset = reversed(queryset)
|
126
|
+
return list(queryset)
|
127
|
+
|
128
|
+
def get_usage_statistics(self, user=None):
|
129
|
+
"""Get message usage statistics."""
|
130
|
+
queryset = self.for_user(user) if user else self.get_queryset()
|
131
|
+
|
132
|
+
return queryset.aggregate(
|
133
|
+
total_messages=Count('id'),
|
134
|
+
user_messages=Count('id', filter=Q(role='user')),
|
135
|
+
assistant_messages=Count('id', filter=Q(role='assistant')),
|
136
|
+
total_tokens=Sum('tokens_used'),
|
137
|
+
total_cost=Sum('cost_usd'),
|
138
|
+
avg_tokens_per_message=Avg('tokens_used'),
|
139
|
+
avg_cost_per_message=Avg('cost_usd'),
|
140
|
+
avg_processing_time=Avg('processing_time_ms')
|
141
|
+
)
|
@@ -0,0 +1,203 @@
|
|
1
|
+
"""
|
2
|
+
Document and DocumentChunk managers.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import List, Dict, Any
|
7
|
+
from django.db import models
|
8
|
+
from django.db.models import Count, Q
|
9
|
+
from django.contrib.auth import get_user_model
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
User = get_user_model()
|
13
|
+
|
14
|
+
|
15
|
+
class DocumentManager(models.Manager):
|
16
|
+
"""Custom manager for Document model."""
|
17
|
+
|
18
|
+
def for_user(self, user):
|
19
|
+
"""Explicitly filter by specific user."""
|
20
|
+
return self.get_queryset().filter(user=user)
|
21
|
+
|
22
|
+
def all_users(self):
|
23
|
+
"""Get unfiltered queryset (admin use)."""
|
24
|
+
return self.get_queryset()
|
25
|
+
|
26
|
+
def processed(self):
|
27
|
+
"""Get only processed documents."""
|
28
|
+
from ..models.base import ProcessingStatus
|
29
|
+
return self.get_queryset().filter(
|
30
|
+
processing_status=ProcessingStatus.COMPLETED
|
31
|
+
)
|
32
|
+
|
33
|
+
def pending_processing(self):
|
34
|
+
"""Get documents pending processing."""
|
35
|
+
from ..models.base import ProcessingStatus
|
36
|
+
return self.get_queryset().filter(
|
37
|
+
processing_status=ProcessingStatus.PENDING
|
38
|
+
)
|
39
|
+
|
40
|
+
def by_content_hash(self, content_hash: str):
|
41
|
+
"""Find documents by content hash."""
|
42
|
+
return self.get_queryset().filter(content_hash=content_hash)
|
43
|
+
|
44
|
+
def with_stats(self):
|
45
|
+
"""Get documents with chunk statistics."""
|
46
|
+
return self.get_queryset().select_related().prefetch_related('chunks')
|
47
|
+
|
48
|
+
def get_vectorization_progress(self, document_id):
|
49
|
+
"""Get vectorization progress for a document."""
|
50
|
+
try:
|
51
|
+
from ..models.document import DocumentChunk
|
52
|
+
|
53
|
+
# Get all chunks for this document
|
54
|
+
chunks_qs = DocumentChunk.objects.filter(document_id=document_id)
|
55
|
+
|
56
|
+
total = chunks_qs.count()
|
57
|
+
|
58
|
+
# Count vectorized chunks by checking if embedding has non-zero values
|
59
|
+
vectorized = 0
|
60
|
+
if total > 0:
|
61
|
+
for chunk in chunks_qs.only('embedding'):
|
62
|
+
if chunk.embedding is not None and len(chunk.embedding) > 0 and any(x != 0.0 for x in chunk.embedding):
|
63
|
+
vectorized += 1
|
64
|
+
|
65
|
+
return {
|
66
|
+
'total': total,
|
67
|
+
'vectorized': vectorized,
|
68
|
+
'percentage': round((vectorized / total * 100) if total > 0 else 0, 1)
|
69
|
+
}
|
70
|
+
except Exception as e:
|
71
|
+
logger.error(f"Error getting vectorization progress for document {document_id}: {e}")
|
72
|
+
return {
|
73
|
+
'total': 0,
|
74
|
+
'vectorized': 0,
|
75
|
+
'percentage': 0
|
76
|
+
}
|
77
|
+
|
78
|
+
def get_vectorization_status_display(self, document):
|
79
|
+
"""Get vectorization status display for admin."""
|
80
|
+
try:
|
81
|
+
# Check processing status first
|
82
|
+
from ..models.document import ProcessingStatus
|
83
|
+
|
84
|
+
if document.processing_status == ProcessingStatus.PENDING:
|
85
|
+
return 'no_chunks', 'Pending'
|
86
|
+
elif document.processing_status == ProcessingStatus.PROCESSING:
|
87
|
+
return 'partial', 'Processing...'
|
88
|
+
elif document.processing_status == ProcessingStatus.FAILED:
|
89
|
+
return 'none', 'Failed'
|
90
|
+
|
91
|
+
progress = self.get_vectorization_progress(document.id)
|
92
|
+
total = progress['total']
|
93
|
+
vectorized = progress['vectorized']
|
94
|
+
percentage = progress['percentage']
|
95
|
+
|
96
|
+
if total == 0:
|
97
|
+
return 'no_chunks', 'No chunks'
|
98
|
+
elif percentage == 100:
|
99
|
+
return 'completed', f'{vectorized}/{total} (100%)'
|
100
|
+
elif percentage > 0:
|
101
|
+
return 'partial', f'{vectorized}/{total} ({percentage}%)'
|
102
|
+
else:
|
103
|
+
return 'none', f'{vectorized}/{total} (0%)'
|
104
|
+
except Exception as e:
|
105
|
+
logger.error(f"Error getting vectorization status for document {document.id}: {e}")
|
106
|
+
|
107
|
+
# Try to provide more specific error information
|
108
|
+
if document.processing_status == ProcessingStatus.COMPLETED and document.chunks_count > 0:
|
109
|
+
return 'none', f'Error ({document.chunks_count} chunks)'
|
110
|
+
else:
|
111
|
+
return 'no_chunks', 'Error'
|
112
|
+
|
113
|
+
def find_duplicates(self, document):
|
114
|
+
"""Find duplicate documents with same content hash."""
|
115
|
+
if not document.content_hash:
|
116
|
+
return self.none()
|
117
|
+
|
118
|
+
return self.get_queryset().filter(
|
119
|
+
user=document.user,
|
120
|
+
content_hash=document.content_hash
|
121
|
+
).exclude(pk=document.pk)
|
122
|
+
|
123
|
+
def get_duplicate_info(self, document):
|
124
|
+
"""Get duplicate information for admin display."""
|
125
|
+
if not document.content_hash:
|
126
|
+
return "No content hash"
|
127
|
+
|
128
|
+
duplicates = self.find_duplicates(document)
|
129
|
+
|
130
|
+
if not duplicates.exists():
|
131
|
+
return "✅ No duplicates found"
|
132
|
+
|
133
|
+
return {
|
134
|
+
'count': duplicates.count(),
|
135
|
+
'duplicates': list(duplicates[:3]) # Return first 3 for display
|
136
|
+
}
|
137
|
+
|
138
|
+
def check_duplicate_before_save(self, user, content, exclude_id=None):
|
139
|
+
"""Check for duplicate content before saving. Returns (is_duplicate, existing_doc)."""
|
140
|
+
if not content:
|
141
|
+
return False, None
|
142
|
+
|
143
|
+
import hashlib
|
144
|
+
content_hash = hashlib.sha256(content.encode()).hexdigest()
|
145
|
+
|
146
|
+
# Use all_users() to bypass user filtering
|
147
|
+
query = self.all_users().filter(user=user, content_hash=content_hash)
|
148
|
+
|
149
|
+
if exclude_id:
|
150
|
+
query = query.exclude(pk=exclude_id)
|
151
|
+
|
152
|
+
existing_doc = query.first()
|
153
|
+
return existing_doc is not None, existing_doc
|
154
|
+
|
155
|
+
|
156
|
+
class DocumentChunkManager(models.Manager):
|
157
|
+
"""Custom manager for DocumentChunk model."""
|
158
|
+
|
159
|
+
def for_user(self, user):
|
160
|
+
"""Explicitly filter by specific user."""
|
161
|
+
return self.get_queryset().filter(user=user)
|
162
|
+
|
163
|
+
def all_users(self):
|
164
|
+
"""Get unfiltered queryset (admin use)."""
|
165
|
+
return self.get_queryset()
|
166
|
+
|
167
|
+
def for_document(self, document_id: str):
|
168
|
+
"""Get chunks for specific document."""
|
169
|
+
return self.get_queryset().filter(document_id=document_id)
|
170
|
+
|
171
|
+
def semantic_search(
|
172
|
+
self,
|
173
|
+
query_embedding: List[float],
|
174
|
+
limit: int = 5,
|
175
|
+
similarity_threshold: float = 0.7
|
176
|
+
):
|
177
|
+
"""Perform semantic search."""
|
178
|
+
from pgvector.django import CosineDistance
|
179
|
+
|
180
|
+
return self.get_queryset().annotate(
|
181
|
+
similarity=1 - CosineDistance('embedding', query_embedding)
|
182
|
+
).filter(
|
183
|
+
similarity__gte=similarity_threshold
|
184
|
+
).order_by('-similarity')[:limit]
|
185
|
+
|
186
|
+
def with_document_info(self):
|
187
|
+
"""Get chunks with document information."""
|
188
|
+
return self.get_queryset().select_related('document')
|
189
|
+
|
190
|
+
def vectorized(self):
|
191
|
+
"""Get only vectorized chunks (with non-zero embeddings)."""
|
192
|
+
# This is a simplified check - in practice you might want to check for non-zero vectors
|
193
|
+
return self.get_queryset().exclude(embedding__isnull=True)
|
194
|
+
|
195
|
+
def by_user_and_similarity(self, user, query_embedding: List[float], limit: int = 10):
|
196
|
+
"""Get chunks for specific user with similarity search."""
|
197
|
+
from pgvector.django import CosineDistance
|
198
|
+
|
199
|
+
return self.get_queryset().filter(
|
200
|
+
user=user
|
201
|
+
).annotate(
|
202
|
+
similarity=1 - CosineDistance('embedding', query_embedding)
|
203
|
+
).order_by('-similarity')[:limit]
|