django-cfg 1.4.0__py3-none-any.whl → 1.4.4__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 +1 -1
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -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/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/urls.py +1 -1
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +3 -3
- django_cfg/modules/base.py +1 -1
- django_cfg/modules/django_currency/examples/__init__.py +3 -0
- django_cfg/modules/django_currency/examples/example_database_usage.py +144 -0
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/README.md +20 -20
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/__init__.py +2 -2
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/client.py +5 -5
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/config.py +3 -3
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/README.md +12 -12
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/__init__.py +1 -1
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/apps.py +4 -4
- django_cfg/modules/{django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard → django_ipc_client/dashboard/templates/django_ipc_dashboard}/base.html +1 -1
- django_cfg/modules/{django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard → django_ipc_client/dashboard/templates/django_ipc_dashboard}/dashboard.html +2 -2
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/urls.py +1 -1
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/urls_admin.py +1 -1
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/views.py +2 -2
- django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/exceptions.py +1 -1
- django_cfg/registry/modules.py +1 -1
- django_cfg/templates/admin/examples/component_class_example.html +156 -0
- django_cfg-1.4.4.dist-info/METADATA +533 -0
- {django_cfg-1.4.0.dist-info → django_cfg-1.4.4.dist-info}/RECORD +36 -28
- django_cfg-1.4.0.dist-info/METADATA +0 -920
- /django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/UNFOLD_INTEGRATION.md +0 -0
- /django_cfg/modules/{django_cfg_rpc_client → django_ipc_client}/dashboard/monitor.py +0 -0
- /django_cfg/modules/{django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard → django_ipc_client/dashboard/static/django_ipc_dashboard}/js/dashboard.js +0 -0
- {django_cfg-1.4.0.dist-info → django_cfg-1.4.4.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.0.dist-info → django_cfg-1.4.4.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.0.dist-info → django_cfg-1.4.4.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
@@ -0,0 +1,161 @@
|
|
1
|
+
"""
|
2
|
+
Simple example demonstrating Django Orchestrator usage.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from pydantic import BaseModel
|
8
|
+
from django.contrib.auth.models import User
|
9
|
+
|
10
|
+
from django_cfg.modules.django_orchestrator import DjangoAgent, SimpleOrchestrator, DjangoDeps, RunContext
|
11
|
+
|
12
|
+
|
13
|
+
# Define output model
|
14
|
+
class GreetingResult(BaseModel):
|
15
|
+
"""Result from greeting agent."""
|
16
|
+
greeting: str
|
17
|
+
personalized: bool
|
18
|
+
user_info: str
|
19
|
+
|
20
|
+
|
21
|
+
# Create agent
|
22
|
+
greeting_agent = DjangoAgent[DjangoDeps, GreetingResult](
|
23
|
+
name="greeting_agent",
|
24
|
+
deps_type=DjangoDeps,
|
25
|
+
output_type=GreetingResult,
|
26
|
+
instructions="Generate personalized greetings for users"
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
# Add tools to agent
|
31
|
+
@greeting_agent.tool
|
32
|
+
async def get_user_info(ctx: RunContext[DjangoDeps]) -> str:
|
33
|
+
"""Get user information for personalization."""
|
34
|
+
user = ctx.deps.user
|
35
|
+
return f"User: {user.username}, Email: {user.email}, Joined: {user.date_joined}"
|
36
|
+
|
37
|
+
|
38
|
+
@greeting_agent.tool
|
39
|
+
async def get_time_of_day(ctx: RunContext[DjangoDeps]) -> str:
|
40
|
+
"""Get current time of day for greeting."""
|
41
|
+
from datetime import datetime
|
42
|
+
hour = datetime.now().hour
|
43
|
+
|
44
|
+
if hour < 12:
|
45
|
+
return "morning"
|
46
|
+
elif hour < 17:
|
47
|
+
return "afternoon"
|
48
|
+
else:
|
49
|
+
return "evening"
|
50
|
+
|
51
|
+
|
52
|
+
async def simple_example():
|
53
|
+
"""Run simple orchestrator example."""
|
54
|
+
print("🤖 Django Orchestrator Simple Example")
|
55
|
+
print("=" * 50)
|
56
|
+
|
57
|
+
# Create test user (in real app, get from request)
|
58
|
+
try:
|
59
|
+
user = await User.objects.aget(username='testuser')
|
60
|
+
except User.DoesNotExist:
|
61
|
+
user = await User.objects.acreate_user(
|
62
|
+
username='testuser',
|
63
|
+
email='test@example.com',
|
64
|
+
first_name='Test',
|
65
|
+
last_name='User'
|
66
|
+
)
|
67
|
+
|
68
|
+
# Create dependencies
|
69
|
+
deps = DjangoDeps(user=user)
|
70
|
+
|
71
|
+
# Create orchestrator
|
72
|
+
orchestrator = SimpleOrchestrator()
|
73
|
+
orchestrator.register_agent(greeting_agent)
|
74
|
+
|
75
|
+
# Execute single agent
|
76
|
+
print("\n1. Single Agent Execution:")
|
77
|
+
results = await orchestrator.execute(
|
78
|
+
pattern="sequential",
|
79
|
+
agents=["greeting_agent"],
|
80
|
+
prompt="Create a personalized greeting",
|
81
|
+
deps=deps
|
82
|
+
)
|
83
|
+
|
84
|
+
result = results[0]
|
85
|
+
print(f"✅ Agent: {result.agent_name}")
|
86
|
+
print(f"✅ Output: {result.output}")
|
87
|
+
print(f"✅ Execution Time: {result.execution_time:.2f}s")
|
88
|
+
print(f"✅ Tokens Used: {result.tokens_used}")
|
89
|
+
|
90
|
+
# Get metrics
|
91
|
+
print("\n2. Agent Metrics:")
|
92
|
+
metrics = greeting_agent.get_metrics()
|
93
|
+
for key, value in metrics.items():
|
94
|
+
print(f"📊 {key}: {value}")
|
95
|
+
|
96
|
+
# Get orchestrator metrics
|
97
|
+
print("\n3. Orchestrator Metrics:")
|
98
|
+
orch_metrics = orchestrator.get_metrics()
|
99
|
+
for key, value in orch_metrics.items():
|
100
|
+
print(f"📈 {key}: {value}")
|
101
|
+
|
102
|
+
|
103
|
+
async def multi_agent_example():
|
104
|
+
"""Run multi-agent orchestrator example."""
|
105
|
+
print("\n🤖 Multi-Agent Example")
|
106
|
+
print("=" * 30)
|
107
|
+
|
108
|
+
# Create additional agents
|
109
|
+
analyzer_agent = DjangoAgent[DjangoDeps, BaseModel](
|
110
|
+
name="content_analyzer",
|
111
|
+
deps_type=DjangoDeps,
|
112
|
+
output_type=BaseModel,
|
113
|
+
instructions="Analyze content sentiment and topics"
|
114
|
+
)
|
115
|
+
|
116
|
+
formatter_agent = DjangoAgent[DjangoDeps, BaseModel](
|
117
|
+
name="content_formatter",
|
118
|
+
deps_type=DjangoDeps,
|
119
|
+
output_type=BaseModel,
|
120
|
+
instructions="Format content for display"
|
121
|
+
)
|
122
|
+
|
123
|
+
# Create orchestrator
|
124
|
+
orchestrator = SimpleOrchestrator()
|
125
|
+
orchestrator.register_agent(analyzer_agent)
|
126
|
+
orchestrator.register_agent(formatter_agent)
|
127
|
+
|
128
|
+
# Get user
|
129
|
+
user = await User.objects.aget(username='testuser')
|
130
|
+
deps = DjangoDeps(user=user)
|
131
|
+
|
132
|
+
# Execute sequential workflow
|
133
|
+
print("\n1. Sequential Workflow:")
|
134
|
+
results = await orchestrator.execute(
|
135
|
+
pattern="sequential",
|
136
|
+
agents=["content_analyzer", "content_formatter"],
|
137
|
+
prompt="Analyze and format this content: Hello world!",
|
138
|
+
deps=deps
|
139
|
+
)
|
140
|
+
|
141
|
+
for i, result in enumerate(results, 1):
|
142
|
+
print(f"Step {i} - {result.agent_name}: {result.success}")
|
143
|
+
|
144
|
+
# Execute parallel workflow
|
145
|
+
print("\n2. Parallel Workflow:")
|
146
|
+
results = await orchestrator.execute(
|
147
|
+
pattern="parallel",
|
148
|
+
agents=["content_analyzer", "content_formatter"],
|
149
|
+
prompt="Process content in parallel",
|
150
|
+
deps=deps,
|
151
|
+
max_concurrent=2
|
152
|
+
)
|
153
|
+
|
154
|
+
successful = sum(1 for r in results if r.success)
|
155
|
+
print(f"✅ {successful}/{len(results)} agents completed successfully")
|
156
|
+
|
157
|
+
|
158
|
+
if __name__ == "__main__":
|
159
|
+
# Run examples
|
160
|
+
asyncio.run(simple_example())
|
161
|
+
asyncio.run(multi_agent_example())
|
@@ -0,0 +1,191 @@
|
|
1
|
+
"""
|
2
|
+
Примеры использования нового ExternalDataManager.
|
3
|
+
|
4
|
+
Этот файл показывает, как легко интегрировать внешние данные в knowbase
|
5
|
+
после рефакторинга.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from django.contrib.auth import get_user_model
|
9
|
+
from django_cfg.apps.knowbase.utils.external_data_manager import ExternalDataManager, quick_add_model, quick_search
|
10
|
+
from django_cfg.apps.knowbase.models.external_data import ExternalDataType
|
11
|
+
|
12
|
+
User = get_user_model()
|
13
|
+
|
14
|
+
|
15
|
+
def example_add_django_model():
|
16
|
+
"""Пример добавления Django модели как внешнего источника данных."""
|
17
|
+
|
18
|
+
# Получаем пользователя
|
19
|
+
user = User.objects.first()
|
20
|
+
if not user:
|
21
|
+
print("❌ No users found")
|
22
|
+
return
|
23
|
+
|
24
|
+
# Создаем менеджер
|
25
|
+
manager = ExternalDataManager(user)
|
26
|
+
|
27
|
+
# Добавляем модель Vehicle (если она существует)
|
28
|
+
try:
|
29
|
+
from apps.vehicles_data.models import Vehicle
|
30
|
+
|
31
|
+
external_data = manager.add_django_model(
|
32
|
+
model_class=Vehicle,
|
33
|
+
title="Vehicle Database",
|
34
|
+
fields=['brand__name', 'model', 'year', 'description'],
|
35
|
+
description="All vehicles from the database",
|
36
|
+
search_fields=['brand__name', 'model'],
|
37
|
+
chunk_size=800,
|
38
|
+
overlap_size=150,
|
39
|
+
auto_vectorize=True
|
40
|
+
)
|
41
|
+
|
42
|
+
print(f"✅ Added Vehicle model as external data: {external_data.id}")
|
43
|
+
print(f" Status: {external_data.status}")
|
44
|
+
print(f" Total chunks: {external_data.total_chunks}")
|
45
|
+
|
46
|
+
except ImportError:
|
47
|
+
print("⚠️ Vehicle model not found, using example data instead")
|
48
|
+
|
49
|
+
# Добавляем произвольные данные
|
50
|
+
external_data = manager.add_custom_data(
|
51
|
+
title="Sample Car Data",
|
52
|
+
identifier="sample_cars",
|
53
|
+
content="""
|
54
|
+
Toyota Camry 2023: Reliable sedan with excellent fuel economy
|
55
|
+
Honda Civic 2023: Compact car perfect for city driving
|
56
|
+
BMW X5 2023: Luxury SUV with advanced features
|
57
|
+
Tesla Model 3 2023: Electric vehicle with autopilot
|
58
|
+
""",
|
59
|
+
description="Sample car data for testing",
|
60
|
+
tags=['cars', 'vehicles', 'sample']
|
61
|
+
)
|
62
|
+
|
63
|
+
print(f"✅ Added sample car data: {external_data.id}")
|
64
|
+
|
65
|
+
|
66
|
+
def example_search_external_data():
|
67
|
+
"""Пример поиска по внешним данным."""
|
68
|
+
|
69
|
+
user = User.objects.first()
|
70
|
+
if not user:
|
71
|
+
print("❌ No users found")
|
72
|
+
return
|
73
|
+
|
74
|
+
manager = ExternalDataManager(user)
|
75
|
+
|
76
|
+
# Поиск по запросу
|
77
|
+
results = manager.search(
|
78
|
+
query="reliable car with good fuel economy",
|
79
|
+
limit=3,
|
80
|
+
threshold=0.6
|
81
|
+
)
|
82
|
+
|
83
|
+
print(f"🔍 Search results ({len(results)} found):")
|
84
|
+
for i, result in enumerate(results, 1):
|
85
|
+
print(f" {i}. {result['source_title']} (similarity: {result['similarity']:.3f})")
|
86
|
+
print(f" Content: {result['content'][:100]}...")
|
87
|
+
print()
|
88
|
+
|
89
|
+
|
90
|
+
def example_get_statistics():
|
91
|
+
"""Пример получения статистики."""
|
92
|
+
|
93
|
+
user = User.objects.first()
|
94
|
+
if not user:
|
95
|
+
print("❌ No users found")
|
96
|
+
return
|
97
|
+
|
98
|
+
manager = ExternalDataManager(user)
|
99
|
+
stats = manager.get_statistics()
|
100
|
+
|
101
|
+
print("📊 External Data Statistics:")
|
102
|
+
print(f" Total sources: {stats.total_sources}")
|
103
|
+
print(f" Active sources: {stats.active_sources}")
|
104
|
+
print(f" Processed sources: {stats.processed_sources}")
|
105
|
+
print(f" Failed sources: {stats.failed_sources}")
|
106
|
+
print(f" Total chunks: {stats.total_chunks}")
|
107
|
+
print(f" Total tokens: {stats.total_tokens}")
|
108
|
+
print(f" Total cost: ${stats.total_cost:.4f}")
|
109
|
+
print(f" Source types: {stats.source_type_counts}")
|
110
|
+
|
111
|
+
|
112
|
+
def example_health_check():
|
113
|
+
"""Пример проверки здоровья системы."""
|
114
|
+
|
115
|
+
user = User.objects.first()
|
116
|
+
if not user:
|
117
|
+
print("❌ No users found")
|
118
|
+
return
|
119
|
+
|
120
|
+
manager = ExternalDataManager(user)
|
121
|
+
health = manager.health_check()
|
122
|
+
|
123
|
+
print("🏥 System Health Check:")
|
124
|
+
print(f" Status: {health.status}")
|
125
|
+
print(f" Healthy: {'✅' if health.healthy else '❌'}")
|
126
|
+
print(f" Database: {'✅' if health.database_healthy else '❌'}")
|
127
|
+
print(f" Embedding Service: {'✅' if health.embedding_service_healthy else '❌'}")
|
128
|
+
print(f" Processing: {'✅' if health.processing_healthy else '❌'}")
|
129
|
+
print(f" Response time: {health.response_time_ms:.2f}ms")
|
130
|
+
print(f" Active sources: {health.active_sources}")
|
131
|
+
print(f" Pending processing: {health.pending_processing}")
|
132
|
+
print(f" Failed processing: {health.failed_processing}")
|
133
|
+
|
134
|
+
if health.issues:
|
135
|
+
print(f" Issues: {health.issues}")
|
136
|
+
if health.warnings:
|
137
|
+
print(f" Warnings: {health.warnings}")
|
138
|
+
|
139
|
+
|
140
|
+
def example_quick_functions():
|
141
|
+
"""Пример использования быстрых функций."""
|
142
|
+
|
143
|
+
user = User.objects.first()
|
144
|
+
if not user:
|
145
|
+
print("❌ No users found")
|
146
|
+
return
|
147
|
+
|
148
|
+
# Быстрый поиск
|
149
|
+
results = quick_search(
|
150
|
+
user=user,
|
151
|
+
query="electric vehicle",
|
152
|
+
limit=2
|
153
|
+
)
|
154
|
+
|
155
|
+
print(f"⚡ Quick search results: {len(results)} found")
|
156
|
+
for result in results:
|
157
|
+
print(f" - {result['source_title']}: {result['similarity']:.3f}")
|
158
|
+
|
159
|
+
|
160
|
+
def run_all_examples():
|
161
|
+
"""Запустить все примеры."""
|
162
|
+
|
163
|
+
print("🚀 Running External Data Manager Examples")
|
164
|
+
print("=" * 50)
|
165
|
+
|
166
|
+
try:
|
167
|
+
print("\n1. Adding Django Model:")
|
168
|
+
example_add_django_model()
|
169
|
+
|
170
|
+
print("\n2. Searching External Data:")
|
171
|
+
example_search_external_data()
|
172
|
+
|
173
|
+
print("\n3. Getting Statistics:")
|
174
|
+
example_get_statistics()
|
175
|
+
|
176
|
+
print("\n4. Health Check:")
|
177
|
+
example_health_check()
|
178
|
+
|
179
|
+
print("\n5. Quick Functions:")
|
180
|
+
example_quick_functions()
|
181
|
+
|
182
|
+
print("\n✅ All examples completed successfully!")
|
183
|
+
|
184
|
+
except Exception as e:
|
185
|
+
print(f"\n❌ Error running examples: {e}")
|
186
|
+
import traceback
|
187
|
+
traceback.print_exc()
|
188
|
+
|
189
|
+
|
190
|
+
if __name__ == "__main__":
|
191
|
+
run_all_examples()
|
@@ -0,0 +1,199 @@
|
|
1
|
+
"""
|
2
|
+
Example of using ExternalDataMixin with VehicleModel.
|
3
|
+
|
4
|
+
This shows how to integrate VehicleModel with knowbase using the mixin.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.db import models
|
8
|
+
from django_cfg.apps.knowbase.mixins import ExternalDataMixin
|
9
|
+
from django_cfg.apps.knowbase.models.external_data import ExternalDataType
|
10
|
+
|
11
|
+
|
12
|
+
class VehicleModelWithMixin(ExternalDataMixin, models.Model):
|
13
|
+
"""
|
14
|
+
Example VehicleModel with ExternalDataMixin integration.
|
15
|
+
|
16
|
+
This replaces the manual integration we had before with automatic
|
17
|
+
tracking and vectorization.
|
18
|
+
"""
|
19
|
+
|
20
|
+
# Original VehicleModel fields
|
21
|
+
brand = models.ForeignKey('vehicles_data.Brand', on_delete=models.CASCADE)
|
22
|
+
code = models.CharField(max_length=20)
|
23
|
+
name = models.CharField(max_length=100)
|
24
|
+
body_type = models.CharField(max_length=20, blank=True)
|
25
|
+
segment = models.CharField(max_length=50, blank=True)
|
26
|
+
is_active = models.BooleanField(default=True)
|
27
|
+
total_vehicles = models.PositiveIntegerField(default=0)
|
28
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
29
|
+
updated_at = models.DateTimeField(auto_now=True)
|
30
|
+
|
31
|
+
class Meta:
|
32
|
+
abstract = True # This is just an example
|
33
|
+
|
34
|
+
class ExternalDataMeta:
|
35
|
+
# Fields to watch for changes - only update when these change
|
36
|
+
watch_fields = ['name', 'body_type', 'segment', 'is_active']
|
37
|
+
|
38
|
+
# Lower threshold for multilingual vehicle data
|
39
|
+
similarity_threshold = 0.4
|
40
|
+
|
41
|
+
# Vehicle models are model type
|
42
|
+
source_type = ExternalDataType.MODEL
|
43
|
+
|
44
|
+
# Enable auto-sync
|
45
|
+
auto_sync = True
|
46
|
+
|
47
|
+
# Make public for search
|
48
|
+
is_public = True
|
49
|
+
|
50
|
+
# Required: content generation method
|
51
|
+
def get_external_content(self):
|
52
|
+
"""Generate content for vectorization."""
|
53
|
+
content_parts = [
|
54
|
+
f"# {self.brand.name} {self.name}",
|
55
|
+
"",
|
56
|
+
"## Basic Information",
|
57
|
+
f"- **Brand**: {self.brand.name} ({self.brand.code})",
|
58
|
+
f"- **Model**: {self.name} ({self.code})",
|
59
|
+
]
|
60
|
+
|
61
|
+
if self.body_type:
|
62
|
+
content_parts.append(f"- **Body Type**: {self.body_type}")
|
63
|
+
|
64
|
+
if self.segment:
|
65
|
+
content_parts.append(f"- **Market Segment**: {self.segment}")
|
66
|
+
|
67
|
+
content_parts.extend([
|
68
|
+
f"- **Status**: {'Active' if self.is_active else 'Inactive'}",
|
69
|
+
"",
|
70
|
+
"## Market Statistics",
|
71
|
+
f"- **Total Listings**: {self.total_vehicles:,} vehicles available",
|
72
|
+
])
|
73
|
+
|
74
|
+
if hasattr(self, 'vehicles') and self.vehicles.exists():
|
75
|
+
# Add some vehicle statistics if available
|
76
|
+
vehicles = self.vehicles.filter(is_active=True)
|
77
|
+
if vehicles.exists():
|
78
|
+
content_parts.extend([
|
79
|
+
"",
|
80
|
+
"## Available Vehicles",
|
81
|
+
f"- **Active Listings**: {vehicles.count():,} vehicles",
|
82
|
+
])
|
83
|
+
|
84
|
+
# Add price range if available
|
85
|
+
if hasattr(vehicles.first(), 'price'):
|
86
|
+
prices = vehicles.exclude(price__isnull=True).values_list('price', flat=True)
|
87
|
+
if prices:
|
88
|
+
min_price = min(prices)
|
89
|
+
max_price = max(prices)
|
90
|
+
content_parts.append(f"- **Price Range**: ${min_price:,} - ${max_price:,}")
|
91
|
+
|
92
|
+
content_parts.extend([
|
93
|
+
"",
|
94
|
+
f"## Model History",
|
95
|
+
f"- **First Listed**: {self.created_at.strftime('%Y-%m-%d')}",
|
96
|
+
f"- **Last Updated**: {self.updated_at.strftime('%Y-%m-%d')}",
|
97
|
+
])
|
98
|
+
|
99
|
+
return "\n".join(content_parts)
|
100
|
+
|
101
|
+
# Optional: custom title
|
102
|
+
def get_external_title(self):
|
103
|
+
"""Generate title for ExternalData."""
|
104
|
+
return f"Vehicle Model: {self.brand.name} {self.name}"
|
105
|
+
|
106
|
+
# Optional: custom description
|
107
|
+
def get_external_description(self):
|
108
|
+
"""Generate description for ExternalData."""
|
109
|
+
parts = [f"Comprehensive information about {self.brand.name} {self.name}"]
|
110
|
+
|
111
|
+
if self.body_type:
|
112
|
+
parts.append(f"({self.body_type})")
|
113
|
+
|
114
|
+
parts.append("including specifications, market data, and vehicle listings.")
|
115
|
+
|
116
|
+
return " ".join(parts)
|
117
|
+
|
118
|
+
# Optional: metadata
|
119
|
+
def get_external_metadata(self):
|
120
|
+
"""Generate metadata for ExternalData."""
|
121
|
+
return {
|
122
|
+
'vehicle_model_id': str(self.id),
|
123
|
+
'brand_code': self.brand.code,
|
124
|
+
'brand_name': self.brand.name,
|
125
|
+
'model_code': self.code,
|
126
|
+
'model_name': self.name,
|
127
|
+
'body_type': self.body_type,
|
128
|
+
'segment': self.segment,
|
129
|
+
'total_vehicles': self.total_vehicles,
|
130
|
+
'is_active': self.is_active,
|
131
|
+
'created_at': self.created_at.isoformat(),
|
132
|
+
'updated_at': self.updated_at.isoformat(),
|
133
|
+
'integration_type': 'vehicle_model_mixin_auto'
|
134
|
+
}
|
135
|
+
|
136
|
+
# Optional: tags
|
137
|
+
def get_external_tags(self):
|
138
|
+
"""Generate tags for ExternalData."""
|
139
|
+
tags = [
|
140
|
+
'vehicle',
|
141
|
+
'model',
|
142
|
+
self.brand.code.lower(),
|
143
|
+
self.code.lower(),
|
144
|
+
self.brand.name.lower().replace(' ', '_'),
|
145
|
+
self.name.lower().replace(' ', '_'),
|
146
|
+
]
|
147
|
+
|
148
|
+
if self.body_type:
|
149
|
+
tags.append(self.body_type.lower().replace(' ', '_'))
|
150
|
+
|
151
|
+
if self.segment:
|
152
|
+
tags.append(self.segment.lower().replace(' ', '_'))
|
153
|
+
|
154
|
+
return tags
|
155
|
+
|
156
|
+
@property
|
157
|
+
def full_name(self):
|
158
|
+
"""Get full model name with brand."""
|
159
|
+
return f"{self.brand.name} {self.name}"
|
160
|
+
|
161
|
+
def __str__(self):
|
162
|
+
return self.full_name
|
163
|
+
|
164
|
+
|
165
|
+
# Usage example:
|
166
|
+
"""
|
167
|
+
# To use this mixin in your existing VehicleModel:
|
168
|
+
|
169
|
+
1. Add the mixin to your model:
|
170
|
+
class VehicleModel(ExternalDataMixin, models.Model):
|
171
|
+
# ... your existing fields ...
|
172
|
+
|
173
|
+
class ExternalDataMeta:
|
174
|
+
watch_fields = ['name', 'body_type', 'segment', 'is_active']
|
175
|
+
similarity_threshold = 0.4
|
176
|
+
source_type = ExternalDataType.MODEL
|
177
|
+
auto_sync = True
|
178
|
+
is_public = True
|
179
|
+
|
180
|
+
def get_external_content(self):
|
181
|
+
# ... content generation logic ...
|
182
|
+
return content
|
183
|
+
|
184
|
+
2. Run migrations to add the mixin fields:
|
185
|
+
python manage.py makemigrations
|
186
|
+
python manage.py migrate
|
187
|
+
|
188
|
+
3. That's it! The mixin will automatically:
|
189
|
+
- Create ExternalData when VehicleModel is created
|
190
|
+
- Update ExternalData when watched fields change
|
191
|
+
- Delete ExternalData when VehicleModel is deleted
|
192
|
+
- Handle vectorization and search integration
|
193
|
+
|
194
|
+
4. Manual operations (if needed):
|
195
|
+
vehicle_model.regenerate_external_data() # Force regeneration
|
196
|
+
vehicle_model.delete_external_data() # Remove integration
|
197
|
+
vehicle_model.has_external_data # Check if linked
|
198
|
+
vehicle_model.external_data_status # Get processing status
|
199
|
+
"""
|
django_cfg/apps/urls.py
CHANGED
@@ -57,7 +57,7 @@ def get_django_cfg_urlpatterns() -> List[URLPattern]:
|
|
57
57
|
|
58
58
|
# RPC Dashboard - WebSocket & RPC activity monitoring
|
59
59
|
if base_module.is_rpc_enabled():
|
60
|
-
patterns.append(path('admin/rpc/', include('django_cfg.modules.
|
60
|
+
patterns.append(path('admin/rpc/', include('django_cfg.modules.django_ipc_client.dashboard.urls_admin')))
|
61
61
|
|
62
62
|
except Exception:
|
63
63
|
# Fallback: include all URLs if config is not available
|
@@ -30,7 +30,7 @@ from ...models import (
|
|
30
30
|
from ...models.tasks import TaskConfig
|
31
31
|
from ...models.payments import PaymentsConfig
|
32
32
|
from ...models.ngrok import NgrokConfig
|
33
|
-
from ...modules.
|
33
|
+
from ...modules.django_ipc_client.config import DjangoCfgRPCConfig
|
34
34
|
|
35
35
|
|
36
36
|
class DjangoConfig(BaseModel):
|
@@ -305,7 +305,7 @@ class DjangoConfig(BaseModel):
|
|
305
305
|
)
|
306
306
|
|
307
307
|
# === RPC Client Configuration ===
|
308
|
-
|
308
|
+
django_ipc: Optional[DjangoCfgRPCConfig] = Field(
|
309
309
|
default=None,
|
310
310
|
description="Django-CFG RPC Client configuration (WebSocket RPC communication)",
|
311
311
|
)
|
@@ -133,8 +133,8 @@ class InstalledAppsBuilder:
|
|
133
133
|
if self.config.payments and self.config.payments.enabled:
|
134
134
|
apps.append("django_cfg.apps.payments")
|
135
135
|
|
136
|
-
if self.config.
|
137
|
-
apps.append("django_cfg.modules.
|
136
|
+
if self.config.django_ipc and self.config.django_ipc.enabled:
|
137
|
+
apps.append("django_cfg.modules.django_ipc_client.dashboard")
|
138
138
|
|
139
139
|
return apps
|
140
140
|
|
@@ -123,11 +123,11 @@ class ThirdPartyIntegrationsGenerator:
|
|
123
123
|
Returns:
|
124
124
|
Dictionary with RPC configuration
|
125
125
|
"""
|
126
|
-
if not hasattr(self.config, "
|
126
|
+
if not hasattr(self.config, "django_ipc") or not self.config.django_ipc:
|
127
127
|
return {}
|
128
128
|
|
129
|
-
rpc_settings = self.config.
|
130
|
-
self.integrations.append("
|
129
|
+
rpc_settings = self.config.django_ipc.to_django_settings()
|
130
|
+
self.integrations.append("django_ipc")
|
131
131
|
|
132
132
|
return rpc_settings
|
133
133
|
|
django_cfg/modules/base.py
CHANGED
@@ -197,7 +197,7 @@ class BaseCfgModule(ABC):
|
|
197
197
|
Returns:
|
198
198
|
True if RPC Client is enabled, False otherwise
|
199
199
|
"""
|
200
|
-
rpc_config = self._get_config_key('
|
200
|
+
rpc_config = self._get_config_key('django_ipc', None)
|
201
201
|
|
202
202
|
# Only handle DjangoCfgRPCConfig model
|
203
203
|
if rpc_config and hasattr(rpc_config, 'enabled'):
|