from django.db import models from django.core.exceptions import ValidationError class IntegrationType(models.TextChoices): MARKETPLACE = 'marketplace', 'Маркетплейс' PAYMENT = 'payment', 'Платёжная система' SHIPPING = 'shipping', 'Служба доставки' class BaseIntegration(models.Model): """ Абстрактный базовый класс для всех интеграций. Singleton-паттерн: каждая конкретная интеграция имеет только одну запись в БД. Поле is_active служит глобальным тумблером включения/выключения. """ integration_type = models.CharField( max_length=20, choices=IntegrationType.choices, editable=False, verbose_name="Тип интеграции" ) is_active = models.BooleanField( default=False, verbose_name="Активна", db_index=True, help_text="Глобальный тумблер включения интеграции" ) name = models.CharField( max_length=100, blank=True, default='', verbose_name="Название", help_text="Произвольное название для удобства (опционально)" ) created_at = models.DateTimeField( auto_now_add=True, verbose_name="Дата создания" ) updated_at = models.DateTimeField( auto_now=True, verbose_name="Дата обновления" ) # Дополнительные настройки в JSON для гибкости extra_config = models.JSONField( default=dict, blank=True, verbose_name="Доп. настройки" ) class Meta: abstract = True ordering = ['-is_active', 'name'] indexes = [ models.Index(fields=['is_active', 'integration_type']), ] # Singleton: только одна запись на тип интеграции constraints = [ models.UniqueConstraint( fields=['integration_type'], name='%(class)s_singleton' ) ] def __str__(self): status = "вкл" if self.is_active else "выкл" return f"{self.get_integration_type_display()}: {self.name or self._meta.verbose_name} ({status})" @property def is_configured(self) -> bool: """ Проверить, есть ли необходимые credentials. Переопределить в наследниках. """ return False def clean(self): """Валидация: нельзя включить ненастроенную интеграцию""" if self.is_active and not self.is_configured: raise ValidationError( 'Невозможно включить интеграцию без настроенных credentials' )