Enhance ImagesViewSet and Images component with site and sector filtering
- Added site_id and sector_id query parameter support in ImagesViewSet for filtering content and task-linked images. - Implemented event listeners in the Images component to refresh data on site and sector changes. - Updated image prompt handling to allow undefined values.
This commit is contained in:
232
backend/igny8_core/api/tests/test_ai_framework.py
Normal file
232
backend/igny8_core/api/tests/test_ai_framework.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Unit tests for AI framework
|
||||
Tests get_model_config() and AICore.run_ai_request() functions
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from igny8_core.auth.models import Account, User, Plan
|
||||
from igny8_core.modules.system.models import IntegrationSettings
|
||||
from igny8_core.ai.settings import get_model_config
|
||||
from igny8_core.ai.ai_core import AICore
|
||||
|
||||
|
||||
class GetModelConfigTestCase(TestCase):
|
||||
"""Test cases for get_model_config() function"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
# Create plan first
|
||||
self.plan = Plan.objects.create(
|
||||
name="Test Plan",
|
||||
slug="test-plan",
|
||||
price=0,
|
||||
credits_per_month=1000
|
||||
)
|
||||
|
||||
# Create user first (Account needs owner)
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@test.com',
|
||||
password='testpass123',
|
||||
role='owner'
|
||||
)
|
||||
|
||||
# Create account with owner
|
||||
self.account = Account.objects.create(
|
||||
name='Test Account',
|
||||
slug='test-account',
|
||||
plan=self.plan,
|
||||
owner=self.user,
|
||||
status='active'
|
||||
)
|
||||
|
||||
# Update user to have account
|
||||
self.user.account = self.account
|
||||
self.user.save()
|
||||
|
||||
def test_get_model_config_with_valid_settings(self):
|
||||
"""Test get_model_config() with valid IntegrationSettings"""
|
||||
IntegrationSettings.objects.create(
|
||||
integration_type='openai',
|
||||
account=self.account,
|
||||
is_active=True,
|
||||
config={
|
||||
'model': 'gpt-4o',
|
||||
'max_tokens': 4000,
|
||||
'temperature': 0.7,
|
||||
'apiKey': 'test-key'
|
||||
}
|
||||
)
|
||||
|
||||
config = get_model_config('auto_cluster', self.account)
|
||||
|
||||
self.assertEqual(config['model'], 'gpt-4o')
|
||||
self.assertEqual(config['max_tokens'], 4000)
|
||||
self.assertEqual(config['temperature'], 0.7)
|
||||
self.assertIn('response_format', config)
|
||||
|
||||
def test_get_model_config_without_account(self):
|
||||
"""Test get_model_config() without account - should raise ValueError"""
|
||||
with self.assertRaises(ValueError) as context:
|
||||
get_model_config('auto_cluster', None)
|
||||
|
||||
self.assertIn('Account is required', str(context.exception))
|
||||
|
||||
def test_get_model_config_without_integration_settings(self):
|
||||
"""Test get_model_config() without IntegrationSettings - should raise ValueError"""
|
||||
with self.assertRaises(ValueError) as context:
|
||||
get_model_config('auto_cluster', self.account)
|
||||
|
||||
self.assertIn('OpenAI IntegrationSettings not configured', str(context.exception))
|
||||
self.assertIn(str(self.account.id), str(context.exception))
|
||||
|
||||
def test_get_model_config_without_model_in_config(self):
|
||||
"""Test get_model_config() without model in config - should raise ValueError"""
|
||||
IntegrationSettings.objects.create(
|
||||
integration_type='openai',
|
||||
account=self.account,
|
||||
is_active=True,
|
||||
config={
|
||||
'max_tokens': 4000,
|
||||
'temperature': 0.7,
|
||||
'apiKey': 'test-key'
|
||||
# No 'model' key
|
||||
}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
get_model_config('auto_cluster', self.account)
|
||||
|
||||
self.assertIn('Model not configured in IntegrationSettings', str(context.exception))
|
||||
self.assertIn(str(self.account.id), str(context.exception))
|
||||
|
||||
def test_get_model_config_with_inactive_settings(self):
|
||||
"""Test get_model_config() with inactive IntegrationSettings - should raise ValueError"""
|
||||
IntegrationSettings.objects.create(
|
||||
integration_type='openai',
|
||||
account=self.account,
|
||||
is_active=False,
|
||||
config={
|
||||
'model': 'gpt-4o',
|
||||
'max_tokens': 4000,
|
||||
'temperature': 0.7
|
||||
}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
get_model_config('auto_cluster', self.account)
|
||||
|
||||
self.assertIn('OpenAI IntegrationSettings not configured', str(context.exception))
|
||||
|
||||
def test_get_model_config_with_function_alias(self):
|
||||
"""Test get_model_config() with function alias"""
|
||||
IntegrationSettings.objects.create(
|
||||
integration_type='openai',
|
||||
account=self.account,
|
||||
is_active=True,
|
||||
config={
|
||||
'model': 'gpt-4o-mini',
|
||||
'max_tokens': 2000,
|
||||
'temperature': 0.5
|
||||
}
|
||||
)
|
||||
|
||||
# Test with alias
|
||||
config1 = get_model_config('cluster_keywords', self.account)
|
||||
config2 = get_model_config('auto_cluster', self.account)
|
||||
|
||||
# Both should return the same config
|
||||
self.assertEqual(config1['model'], config2['model'])
|
||||
self.assertEqual(config1['model'], 'gpt-4o-mini')
|
||||
|
||||
def test_get_model_config_json_mode_models(self):
|
||||
"""Test get_model_config() sets response_format for JSON mode models"""
|
||||
json_models = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo-preview']
|
||||
|
||||
for model in json_models:
|
||||
IntegrationSettings.objects.filter(account=self.account).delete()
|
||||
IntegrationSettings.objects.create(
|
||||
integration_type='openai',
|
||||
account=self.account,
|
||||
is_active=True,
|
||||
config={
|
||||
'model': model,
|
||||
'max_tokens': 4000,
|
||||
'temperature': 0.7
|
||||
}
|
||||
)
|
||||
|
||||
config = get_model_config('auto_cluster', self.account)
|
||||
self.assertIn('response_format', config)
|
||||
self.assertEqual(config['response_format'], {'type': 'json_object'})
|
||||
|
||||
|
||||
class AICoreTestCase(TestCase):
|
||||
"""Test cases for AICore.run_ai_request() function"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
# Create plan first
|
||||
self.plan = Plan.objects.create(
|
||||
name="Test Plan",
|
||||
slug="test-plan",
|
||||
price=0,
|
||||
credits_per_month=1000
|
||||
)
|
||||
|
||||
# Create user first (Account needs owner)
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@test.com',
|
||||
password='testpass123',
|
||||
role='owner'
|
||||
)
|
||||
|
||||
# Create account with owner
|
||||
self.account = Account.objects.create(
|
||||
name='Test Account',
|
||||
slug='test-account',
|
||||
plan=self.plan,
|
||||
owner=self.user,
|
||||
status='active'
|
||||
)
|
||||
|
||||
# Update user to have account
|
||||
self.user.account = self.account
|
||||
self.user.save()
|
||||
|
||||
self.ai_core = AICore(account=self.account)
|
||||
|
||||
def test_run_ai_request_without_model(self):
|
||||
"""Test run_ai_request() without model - should return error dict"""
|
||||
result = self.ai_core.run_ai_request(
|
||||
prompt="Test prompt",
|
||||
model=None,
|
||||
function_name='test_function'
|
||||
)
|
||||
|
||||
self.assertIn('error', result)
|
||||
self.assertIn('Model is required', result['error'])
|
||||
self.assertEqual(result['content'], None)
|
||||
self.assertEqual(result['total_tokens'], 0)
|
||||
|
||||
def test_run_ai_request_with_empty_model(self):
|
||||
"""Test run_ai_request() with empty model string - should return error dict"""
|
||||
result = self.ai_core.run_ai_request(
|
||||
prompt="Test prompt",
|
||||
model="",
|
||||
function_name='test_function'
|
||||
)
|
||||
|
||||
self.assertIn('error', result)
|
||||
self.assertIn('Model is required', result['error'])
|
||||
self.assertEqual(result['content'], None)
|
||||
self.assertEqual(result['total_tokens'], 0)
|
||||
|
||||
def test_get_model_deprecated(self):
|
||||
"""Test get_model() method is deprecated and raises ValueError"""
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.ai_core.get_model('openai')
|
||||
|
||||
self.assertIn('deprecated', str(context.exception).lower())
|
||||
self.assertIn('run_ai_request', str(context.exception))
|
||||
|
||||
@@ -663,17 +663,47 @@ class ImagesViewSet(SiteSectorModelViewSet):
|
||||
|
||||
account = getattr(request, 'account', None)
|
||||
|
||||
# Get site_id and sector_id from query parameters
|
||||
site_id = request.query_params.get('site_id')
|
||||
sector_id = request.query_params.get('sector_id')
|
||||
|
||||
# Get all content that has images (either directly or via task)
|
||||
# First, get content with direct image links
|
||||
queryset = Content.objects.filter(images__isnull=False)
|
||||
if account:
|
||||
queryset = queryset.filter(account=account)
|
||||
|
||||
# Apply site/sector filtering if provided
|
||||
if site_id:
|
||||
try:
|
||||
queryset = queryset.filter(site_id=int(site_id))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if sector_id:
|
||||
try:
|
||||
queryset = queryset.filter(sector_id=int(sector_id))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Also get content from images linked via task
|
||||
task_linked_images = Images.objects.filter(task__isnull=False, content__isnull=True)
|
||||
if account:
|
||||
task_linked_images = task_linked_images.filter(account=account)
|
||||
|
||||
# Apply site/sector filtering to task-linked images
|
||||
if site_id:
|
||||
try:
|
||||
task_linked_images = task_linked_images.filter(site_id=int(site_id))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if sector_id:
|
||||
try:
|
||||
task_linked_images = task_linked_images.filter(sector_id=int(sector_id))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Get content IDs from task-linked images
|
||||
task_content_ids = set()
|
||||
for image in task_linked_images:
|
||||
@@ -694,6 +724,7 @@ class ImagesViewSet(SiteSectorModelViewSet):
|
||||
for content_id in content_ids:
|
||||
try:
|
||||
content = Content.objects.get(id=content_id)
|
||||
|
||||
# Get images linked directly to content OR via task
|
||||
content_images = Images.objects.filter(
|
||||
Q(content=content) | Q(task=content.task)
|
||||
|
||||
Reference in New Issue
Block a user