Обновление админки и представлений для интеграций
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import RecommerceIntegration, WooCommerceIntegration, IntegrationCategoryMapping
|
from .models import RecommerceIntegration, WooCommerceIntegration, IntegrationCategoryMapping, GLMIntegration, OpenRouterIntegration
|
||||||
|
|
||||||
|
|
||||||
@admin.register(RecommerceIntegration)
|
@admin.register(RecommerceIntegration)
|
||||||
@@ -46,3 +46,31 @@ class IntegrationCategoryMappingAdmin(admin.ModelAdmin):
|
|||||||
('Внешняя категория', {'fields': ('external_category_sku', 'external_category_name')}),
|
('Внешняя категория', {'fields': ('external_category_sku', 'external_category_name')}),
|
||||||
('Служебное', {'fields': ('created_at', 'updated_at'), 'classes': ('collapse',)}),
|
('Служебное', {'fields': ('created_at', 'updated_at'), 'classes': ('collapse',)}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(GLMIntegration)
|
||||||
|
class GLMIntegrationAdmin(admin.ModelAdmin):
|
||||||
|
"""Админка для GLM интеграции"""
|
||||||
|
list_display = ['__str__', 'is_active', 'is_configured', 'model_name', 'updated_at']
|
||||||
|
list_filter = ['is_active', 'is_coding_endpoint']
|
||||||
|
readonly_fields = ['created_at', 'updated_at']
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Основное', {'fields': ('name', 'is_active')}),
|
||||||
|
('API настройки', {'fields': ('api_key', 'api_url', 'model_name', 'temperature', 'is_coding_endpoint')}),
|
||||||
|
('Служебное', {'fields': ('created_at', 'updated_at'), 'classes': ('collapse',)}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(OpenRouterIntegration)
|
||||||
|
class OpenRouterIntegrationAdmin(admin.ModelAdmin):
|
||||||
|
"""Админка для OpenRouter интеграции"""
|
||||||
|
list_display = ['__str__', 'is_active', 'is_configured', 'model_name', 'updated_at']
|
||||||
|
list_filter = ['is_active']
|
||||||
|
readonly_fields = ['created_at', 'updated_at']
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
('Основное', {'fields': ('name', 'is_active')}),
|
||||||
|
('API настройки', {'fields': ('api_key', 'api_url', 'model_name', 'temperature', 'max_tokens')}),
|
||||||
|
('Служебное', {'fields': ('created_at', 'updated_at'), 'classes': ('collapse',)}),
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.http import JsonResponse
|
|||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from user_roles.mixins import OwnerRequiredMixin
|
from user_roles.mixins import OwnerRequiredMixin
|
||||||
from .models import RecommerceIntegration, WooCommerceIntegration
|
from .models import RecommerceIntegration, WooCommerceIntegration, GLMIntegration, OpenRouterIntegration
|
||||||
from integrations.recommerce.tasks import sync_products_batch_task
|
from integrations.recommerce.tasks import sync_products_batch_task
|
||||||
|
|
||||||
|
|
||||||
@@ -13,6 +13,8 @@ from integrations.recommerce.tasks import sync_products_batch_task
|
|||||||
INTEGRATION_REGISTRY = {
|
INTEGRATION_REGISTRY = {
|
||||||
'recommerce': (RecommerceIntegration, 'Recommerce', 'Маркетплейс'),
|
'recommerce': (RecommerceIntegration, 'Recommerce', 'Маркетплейс'),
|
||||||
'woocommerce': (WooCommerceIntegration, 'WooCommerce', 'Маркетплейс'),
|
'woocommerce': (WooCommerceIntegration, 'WooCommerce', 'Маркетплейс'),
|
||||||
|
'glm': (GLMIntegration, 'GLM от Z.AI', 'Сервис ИИ'),
|
||||||
|
'openrouter': (OpenRouterIntegration, 'OpenRouter.ai', 'Сервис ИИ'),
|
||||||
# Добавлять новые интеграции здесь:
|
# Добавлять новые интеграции здесь:
|
||||||
# 'shopify': (ShopifyIntegration, 'Shopify', 'Маркетплейс'),
|
# 'shopify': (ShopifyIntegration, 'Shopify', 'Маркетплейс'),
|
||||||
}
|
}
|
||||||
@@ -110,27 +112,51 @@ def test_integration_connection(request, integration_id: str):
|
|||||||
Тестировать соединение с интеграцией.
|
Тестировать соединение с интеграцией.
|
||||||
POST /integrations/test/<integration_id>/
|
POST /integrations/test/<integration_id>/
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
logger.info(f"=== Начало тестирования интеграции: {integration_id} ===")
|
||||||
|
|
||||||
if not hasattr(request, 'user') or not request.user.is_authenticated:
|
if not hasattr(request, 'user') or not request.user.is_authenticated:
|
||||||
|
logger.error("Пользователь не авторизован")
|
||||||
return JsonResponse({'error': 'Unauthorized'}, status=401)
|
return JsonResponse({'error': 'Unauthorized'}, status=401)
|
||||||
|
|
||||||
model = get_integration_model(integration_id)
|
model = get_integration_model(integration_id)
|
||||||
if not model:
|
if not model:
|
||||||
|
logger.error(f"Неизвестная интеграция: {integration_id}")
|
||||||
return JsonResponse({'error': f'Unknown integration: {integration_id}'}, status=404)
|
return JsonResponse({'error': f'Unknown integration: {integration_id}'}, status=404)
|
||||||
|
|
||||||
instance = model.objects.first()
|
instance = model.objects.first()
|
||||||
if not instance:
|
if not instance:
|
||||||
|
logger.error(f"Интеграция {integration_id} не настроена (нет записи в БД)")
|
||||||
return JsonResponse({'error': 'Интеграция не настроена'}, status=400)
|
return JsonResponse({'error': 'Интеграция не настроена'}, status=400)
|
||||||
|
|
||||||
|
logger.info(f"Найдена интеграция: {instance.name}, is_active={instance.is_active}, is_configured={instance.is_configured}")
|
||||||
|
|
||||||
if not instance.is_configured:
|
if not instance.is_configured:
|
||||||
|
logger.error(f"Интеграция {integration_id} не сконфигурирована")
|
||||||
return JsonResponse({'error': 'Заполните настройки интеграции'}, status=400)
|
return JsonResponse({'error': 'Заполните настройки интеграции'}, status=400)
|
||||||
|
|
||||||
# Получить сервис для интеграции
|
# Получить сервис для интеграции
|
||||||
service = get_integration_service(integration_id, instance)
|
try:
|
||||||
if not service:
|
logger.info(f"Попытка получить сервис для {integration_id}...")
|
||||||
return JsonResponse({'error': 'Сервис не реализован'}, status=501)
|
service = get_integration_service(integration_id, instance)
|
||||||
|
if not service:
|
||||||
|
logger.error(f"Сервис для {integration_id} не реализован")
|
||||||
|
return JsonResponse({'error': 'Сервис не реализован'}, status=501)
|
||||||
|
logger.info(f"Сервис успешно создан: {service.__class__.__name__}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при создании сервиса для {integration_id}: {str(e)}", exc_info=True)
|
||||||
|
return JsonResponse({'error': f'Ошибка при создании сервиса: {str(e)}'}, status=500)
|
||||||
|
|
||||||
# Выполнить тест
|
# Выполнить тест
|
||||||
success, message = service.test_connection()
|
try:
|
||||||
|
logger.info(f"Выполнение теста соединения...")
|
||||||
|
success, message = service.test_connection()
|
||||||
|
logger.info(f"Результат теста: success={success}, message={message}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при выполнении теста соединения: {str(e)}", exc_info=True)
|
||||||
|
return JsonResponse({'error': f'Ошибка при тестировании: {str(e)}'}, status=500)
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'success': success,
|
'success': success,
|
||||||
@@ -146,6 +172,12 @@ def get_integration_service(integration_id: str, instance):
|
|||||||
elif integration_id == 'woocommerce':
|
elif integration_id == 'woocommerce':
|
||||||
# TODO: WooCommerceService
|
# TODO: WooCommerceService
|
||||||
return None
|
return None
|
||||||
|
elif integration_id == 'glm':
|
||||||
|
from .services.ai_services.glm_service import GLMIntegrationService
|
||||||
|
return GLMIntegrationService(instance)
|
||||||
|
elif integration_id == 'openrouter':
|
||||||
|
from .services.ai_services.openrouter_service import OpenRouterIntegrationService
|
||||||
|
return OpenRouterIntegrationService(instance)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -285,18 +317,89 @@ def get_integration_form_data(request, integration_id: str):
|
|||||||
def get_form_fields_meta(model):
|
def get_form_fields_meta(model):
|
||||||
"""Получить метаданные полей для построения формы на фронте"""
|
"""Получить метаданные полей для построения формы на фронте"""
|
||||||
fields = []
|
fields = []
|
||||||
for field_name in get_editable_fields(model):
|
editable_fields = get_editable_fields(model)
|
||||||
field = model._meta.get_field(field_name)
|
|
||||||
field_info = {
|
|
||||||
'name': field_name,
|
|
||||||
'label': getattr(field, 'verbose_name', field_name),
|
|
||||||
'help_text': getattr(field, 'help_text', ''),
|
|
||||||
'required': not getattr(field, 'blank', True),
|
|
||||||
'type': 'text', # default
|
|
||||||
}
|
|
||||||
|
|
||||||
# Определить тип поля
|
# Для GLM отображаем только имя и API ключ
|
||||||
if isinstance(field, model._meta.get_field(field_name).__class__):
|
if model.__name__ == 'GLMIntegration':
|
||||||
|
for field_name in editable_fields:
|
||||||
|
if field_name in ['name', 'api_key']:
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
field_info = {
|
||||||
|
'name': field_name,
|
||||||
|
'label': getattr(field, 'verbose_name', field_name),
|
||||||
|
'help_text': getattr(field, 'help_text', ''),
|
||||||
|
'required': not getattr(field, 'blank', True),
|
||||||
|
'type': 'text', # default
|
||||||
|
}
|
||||||
|
|
||||||
|
# Определить тип поля
|
||||||
|
if 'BooleanField' in field.__class__.__name__:
|
||||||
|
field_info['type'] = 'checkbox'
|
||||||
|
elif 'URLField' in field.__class__.__name__:
|
||||||
|
field_info['type'] = 'url'
|
||||||
|
elif 'secret' in field_name.lower() or 'token' in field_name.lower() or 'key' in field_name.lower():
|
||||||
|
field_info['type'] = 'password'
|
||||||
|
|
||||||
|
fields.append(field_info)
|
||||||
|
elif field_name == 'model_name':
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
field_info = {
|
||||||
|
'name': field_name,
|
||||||
|
'label': getattr(field, 'verbose_name', field_name),
|
||||||
|
'help_text': getattr(field, 'help_text', ''),
|
||||||
|
'required': not getattr(field, 'blank', True),
|
||||||
|
'type': 'select', # dropdown
|
||||||
|
'choices': getattr(field, 'choices', [])
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.append(field_info)
|
||||||
|
# Для OpenRouter отображаем только имя, API ключ, модель и температуру
|
||||||
|
elif model.__name__ == 'OpenRouterIntegration':
|
||||||
|
for field_name in editable_fields:
|
||||||
|
if field_name in ['name', 'api_key']:
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
field_info = {
|
||||||
|
'name': field_name,
|
||||||
|
'label': getattr(field, 'verbose_name', field_name),
|
||||||
|
'help_text': getattr(field, 'help_text', ''),
|
||||||
|
'required': not getattr(field, 'blank', True),
|
||||||
|
'type': 'text', # default
|
||||||
|
}
|
||||||
|
|
||||||
|
# Определить тип поля
|
||||||
|
if 'BooleanField' in field.__class__.__name__:
|
||||||
|
field_info['type'] = 'checkbox'
|
||||||
|
elif 'URLField' in field.__class__.__name__:
|
||||||
|
field_info['type'] = 'url'
|
||||||
|
elif 'secret' in field_name.lower() or 'token' in field_name.lower() or 'key' in field_name.lower():
|
||||||
|
field_info['type'] = 'password'
|
||||||
|
|
||||||
|
fields.append(field_info)
|
||||||
|
elif field_name in ['model_name', 'temperature']:
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
field_info = {
|
||||||
|
'name': field_name,
|
||||||
|
'label': getattr(field, 'verbose_name', field_name),
|
||||||
|
'help_text': getattr(field, 'help_text', ''),
|
||||||
|
'required': not getattr(field, 'blank', True),
|
||||||
|
'type': 'select', # dropdown
|
||||||
|
'choices': getattr(field, 'choices', [])
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.append(field_info)
|
||||||
|
else:
|
||||||
|
# Для других интеграций - все редактируемые поля
|
||||||
|
for field_name in editable_fields:
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
field_info = {
|
||||||
|
'name': field_name,
|
||||||
|
'label': getattr(field, 'verbose_name', field_name),
|
||||||
|
'help_text': getattr(field, 'help_text', ''),
|
||||||
|
'required': not getattr(field, 'blank', True),
|
||||||
|
'type': 'text', # default
|
||||||
|
}
|
||||||
|
|
||||||
|
# Определить тип поля
|
||||||
if 'BooleanField' in field.__class__.__name__:
|
if 'BooleanField' in field.__class__.__name__:
|
||||||
field_info['type'] = 'checkbox'
|
field_info['type'] = 'checkbox'
|
||||||
elif 'URLField' in field.__class__.__name__:
|
elif 'URLField' in field.__class__.__name__:
|
||||||
@@ -304,5 +407,6 @@ def get_form_fields_meta(model):
|
|||||||
elif 'secret' in field_name.lower() or 'token' in field_name.lower() or 'key' in field_name.lower():
|
elif 'secret' in field_name.lower() or 'token' in field_name.lower() or 'key' in field_name.lower():
|
||||||
field_info['type'] = 'password'
|
field_info['type'] = 'password'
|
||||||
|
|
||||||
fields.append(field_info)
|
fields.append(field_info)
|
||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
|||||||
@@ -189,6 +189,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<label class="form-check-label" for="field-${field.name}">${field.label}</label>
|
<label class="form-check-label" for="field-${field.name}">${field.label}</label>
|
||||||
${field.help_text ? `<div class="form-text">${field.help_text}</div>` : ''}
|
${field.help_text ? `<div class="form-text">${field.help_text}</div>` : ''}
|
||||||
`;
|
`;
|
||||||
|
} else if (field.type === 'select') {
|
||||||
|
div.innerHTML = `
|
||||||
|
<label class="form-label" for="field-${field.name}">
|
||||||
|
${field.label}
|
||||||
|
${field.required ? '<span class="text-danger">*</span>' : ''}
|
||||||
|
</label>
|
||||||
|
<select class="form-select" id="field-${field.name}"
|
||||||
|
name="${field.name}"
|
||||||
|
${field.required ? 'required' : ''}>
|
||||||
|
${field.choices.map(choice => `
|
||||||
|
<option value="${choice[0]}" ${data[field.name] === choice[0] ? 'selected' : ''}>
|
||||||
|
${choice[1]}
|
||||||
|
</option>
|
||||||
|
`).join('')}
|
||||||
|
</select>
|
||||||
|
${field.help_text ? `<div class="form-text">${field.help_text}</div>` : ''}
|
||||||
|
`;
|
||||||
} else {
|
} else {
|
||||||
const inputType = field.type === 'password' ? 'password' : (field.type === 'url' ? 'url' : 'text');
|
const inputType = field.type === 'password' ? 'password' : (field.type === 'url' ? 'url' : 'text');
|
||||||
const value = data[field.name] || '';
|
const value = data[field.name] || '';
|
||||||
|
|||||||
Reference in New Issue
Block a user