Обновление админки и представлений для интеграций

This commit is contained in:
2026-01-15 12:20:39 +03:00
parent fb3074a2ed
commit c7e03d258b
3 changed files with 174 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
from django.contrib import admin
from .models import RecommerceIntegration, WooCommerceIntegration, IntegrationCategoryMapping
from .models import RecommerceIntegration, WooCommerceIntegration, IntegrationCategoryMapping, GLMIntegration, OpenRouterIntegration
@admin.register(RecommerceIntegration)
@@ -46,3 +46,31 @@ class IntegrationCategoryMappingAdmin(admin.ModelAdmin):
('Внешняя категория', {'fields': ('external_category_sku', 'external_category_name')}),
('Служебное', {'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',)}),
)

View File

@@ -4,7 +4,7 @@ from django.http import JsonResponse
from django.views.decorators.http import require_POST
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
@@ -13,6 +13,8 @@ from integrations.recommerce.tasks import sync_products_batch_task
INTEGRATION_REGISTRY = {
'recommerce': (RecommerceIntegration, 'Recommerce', 'Маркетплейс'),
'woocommerce': (WooCommerceIntegration, 'WooCommerce', 'Маркетплейс'),
'glm': (GLMIntegration, 'GLM от Z.AI', 'Сервис ИИ'),
'openrouter': (OpenRouterIntegration, 'OpenRouter.ai', 'Сервис ИИ'),
# Добавлять новые интеграции здесь:
# 'shopify': (ShopifyIntegration, 'Shopify', 'Маркетплейс'),
}
@@ -110,27 +112,51 @@ def test_integration_connection(request, integration_id: str):
Тестировать соединение с интеграцией.
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:
logger.error("Пользователь не авторизован")
return JsonResponse({'error': 'Unauthorized'}, status=401)
model = get_integration_model(integration_id)
if not model:
logger.error(f"Неизвестная интеграция: {integration_id}")
return JsonResponse({'error': f'Unknown integration: {integration_id}'}, status=404)
instance = model.objects.first()
if not instance:
logger.error(f"Интеграция {integration_id} не настроена (нет записи в БД)")
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:
logger.error(f"Интеграция {integration_id} не сконфигурирована")
return JsonResponse({'error': 'Заполните настройки интеграции'}, status=400)
# Получить сервис для интеграции
service = get_integration_service(integration_id, instance)
if not service:
return JsonResponse({'error': 'Сервис не реализован'}, status=501)
try:
logger.info(f"Попытка получить сервис для {integration_id}...")
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({
'success': success,
@@ -146,6 +172,12 @@ def get_integration_service(integration_id: str, instance):
elif integration_id == 'woocommerce':
# TODO: WooCommerceService
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
@@ -285,18 +317,89 @@ def get_integration_form_data(request, integration_id: str):
def get_form_fields_meta(model):
"""Получить метаданные полей для построения формы на фронте"""
fields = []
for field_name in 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
}
editable_fields = get_editable_fields(model)
# Определить тип поля
if isinstance(field, model._meta.get_field(field_name).__class__):
# Для GLM отображаем только имя и API ключ
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__:
field_info['type'] = 'checkbox'
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():
field_info['type'] = 'password'
fields.append(field_info)
fields.append(field_info)
return fields

View File

@@ -189,6 +189,23 @@ document.addEventListener('DOMContentLoaded', function() {
<label class="form-check-label" for="field-${field.name}">${field.label}</label>
${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 {
const inputType = field.type === 'password' ? 'password' : (field.type === 'url' ? 'url' : 'text');
const value = data[field.name] || '';