From c7e03d258be4d6a476d4cbffc41bc084ee649371 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Thu, 15 Jan 2026 12:20:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D1=80=D0=B5=D0=B4=D1=81=D1=82=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B4=D0=BB=D1=8F=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- myproject/integrations/admin.py | 30 +++- myproject/integrations/views.py | 144 +++++++++++++++--- .../system_settings/integrations.html | 25 ++- 3 files changed, 174 insertions(+), 25 deletions(-) diff --git a/myproject/integrations/admin.py b/myproject/integrations/admin.py index 2c3445f..25ecc74 100644 --- a/myproject/integrations/admin.py +++ b/myproject/integrations/admin.py @@ -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',)}), + ) diff --git a/myproject/integrations/views.py b/myproject/integrations/views.py index 7e3a5aa..9b0739b 100644 --- a/myproject/integrations/views.py +++ b/myproject/integrations/views.py @@ -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// """ + 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 @@ -168,16 +200,16 @@ class RecommerceBatchSyncView(TemplateView): data = json.loads(request.body) product_ids = data.get('product_ids', []) options = data.get('options', {}) - + if not product_ids: return JsonResponse({'error': 'No products selected'}, status=400) - + # Запуск Celery задачи с передачей schema_name from django_tenants.utils import get_tenant_model Tenant = get_tenant_model() schema_name = request.tenant.schema_name task = sync_products_batch_task.delay(product_ids, options, schema_name) - + return JsonResponse({ 'success': True, 'task_id': task.id, @@ -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 diff --git a/myproject/system_settings/templates/system_settings/integrations.html b/myproject/system_settings/templates/system_settings/integrations.html index 61a3969..0c6b3e5 100644 --- a/myproject/system_settings/templates/system_settings/integrations.html +++ b/myproject/system_settings/templates/system_settings/integrations.html @@ -176,11 +176,11 @@ document.addEventListener('DOMContentLoaded', function() { function buildForm(fields, data) { const container = document.getElementById('settings-fields'); container.innerHTML = ''; - + fields.forEach(field => { const div = document.createElement('div'); div.className = 'mb-3'; - + if (field.type === 'checkbox') { div.className = 'form-check mb-3'; div.innerHTML = ` @@ -189,11 +189,28 @@ document.addEventListener('DOMContentLoaded', function() { ${field.help_text ? `
${field.help_text}
` : ''} `; + } else if (field.type === 'select') { + div.innerHTML = ` + + + ${field.help_text ? `
${field.help_text}
` : ''} + `; } else { const inputType = field.type === 'password' ? 'password' : (field.type === 'url' ? 'url' : 'text'); const value = data[field.name] || ''; const placeholder = field.type === 'password' && value === '........' ? 'Введите новое значение для изменения' : ''; - + div.innerHTML = `