import logging from django.contrib.auth.models import User from django.db import models from django.urls import reverse from django.utils import timezone from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.core.cache import cache from .utils.email_notifications import send_callback_notification logger = logging.getLogger(__name__) class Recall(models.Model): title = models.CharField(max_length=255, verbose_name='Организация') content = models.TextField(blank=True, verbose_name='Отзыв') scan = models.ImageField(upload_to="scan/%Y/%m/%d/", blank=True, null=True, verbose_name='Фото') time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения') is_published = models.BooleanField(default=True, verbose_name='Опубликован') def __str__(self): return self.title def get_absolute_url(self): return reverse('recall_detail', kwargs={'pk': self.pk}) class Meta: verbose_name = 'Отзыв' verbose_name_plural = 'Отзывы' ordering = ['time_create', 'title'] def get_seo_title(self): return f"Отзыв от {self.title} | Программист 1С" def get_seo_description(self): if self.content: clean_content = self.content[:160].replace('\n', ' ').strip() return f"Отзыв о работе программиста 1С от {self.title}. {clean_content}..." return f"Отзыв клиента {self.title} о работе программиста 1С Николая Сердюк" class Competence(models.Model): title = models.CharField(max_length=255, verbose_name='Программист') content = models.TextField(blank=True, verbose_name='Компетенция') photo = models.ImageField(upload_to="photos/%Y/%m/%d/", blank=True, null=True, verbose_name='Фото') time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения') is_published = models.BooleanField(default=True, verbose_name='Опубликован') def __str__(self): return self.title def get_absolute_url(self): return reverse('competence_detail', kwargs={'pk': self.pk}) class Meta: verbose_name = 'Компетенция' verbose_name_plural = 'Компетенции' ordering = ['time_create', 'title'] class Solution(models.Model): title = models.CharField(max_length=255, verbose_name='Наименование') description = models.TextField(blank=True, verbose_name='Описание') implementation = models.TextField(blank=True, verbose_name='Реализация') closing = models.TextField(blank=True, verbose_name='Заключение') time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения') is_published = models.BooleanField(default=True, verbose_name='Опубликован') slug = models.SlugField( max_length=255, unique=False, # временно не уникальное db_index=True, verbose_name='URL-идентификатор', blank=True, null=True, # разрешаем NULL ) def __str__(self): return self.title def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.title) super().save(*args, **kwargs) def get_absolute_url(self): return reverse('solution_detail', kwargs={'slug': self.slug}) class Meta: verbose_name = 'Проекты' verbose_name_plural = 'Проекты' ordering = ['time_create', 'title'] def get_seo_title(self): return f"Проект: {self.title} | Автоматизация 1С" def get_seo_description(self): # Используем closing если есть, иначе description source = self.closing or self.description if source: clean = source[:160].replace('\n', ' ').strip() return f"Проект автоматизации: {self.title}. {clean}..." return f"Реализация проекта {self.title} - программист 1С Николай Сердюк" def get_meta_keywords(self): base_keywords = ["проект 1С", "автоматизация 1С", "внедрение 1С"] title_words = self.title.lower().split() return base_keywords + title_words class Home(models.Model): title = models.CharField(max_length=255, verbose_name='Наименование') content = models.TextField(blank=True, verbose_name='Статья') home_image = models.ImageField(upload_to="home_image/%Y/%m/%d/", blank=True, null=True, verbose_name='Фото') time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения') is_published = models.BooleanField(default=True, verbose_name='Опубликован') def __str__(self): return self.title def get_absolute_url(self): return reverse('home') class Meta: verbose_name = 'Главная страница' verbose_name_plural = 'Главная страница' ordering = ['time_create', 'title'] class CallbackRequest(models.Model): name = models.CharField(max_length=100, verbose_name='Имя') phone = models.CharField(max_length=20, verbose_name='Телефон') email = models.EmailField(blank=True, null=True, verbose_name='Электронная почта') question = models.TextField(blank=True, verbose_name='Ваш вопрос') time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') is_processed = models.BooleanField(default=False, verbose_name='Обработано') is_read = models.BooleanField(default=False, verbose_name='Прочитано') notification_sent = models.BooleanField(default=False, verbose_name='Уведомление отправлено') def __str__(self): return f"{self.name} - {self.phone}" class Meta: verbose_name = 'Заявка на звонок' verbose_name_plural = 'Заявки на звонок' ordering = ['-time_create'] @receiver(post_save, sender=CallbackRequest) def send_callback_email_notification(sender, instance, created, **kwargs): if created and not instance.notification_sent: try: success = send_callback_notification(instance) if success: sender.objects.filter(pk=instance.pk).update(notification_sent=True) except Exception: # Логируем ошибку, но не прерываем сохранение объекта logger.exception( 'Не удалось отправить email-уведомление для заявки pk=%s', instance.pk ) class PageView(models.Model): url = models.CharField(max_length=500) timestamp = models.DateTimeField(default=timezone.now) ip_address = models.GenericIPAddressField() user_agent = models.TextField(blank=True) referer = models.CharField(max_length=500, blank=True) class Meta: indexes = [ models.Index(fields=['url', 'timestamp']), models.Index(fields=['timestamp']), ] class Visitor(models.Model): ip_address = models.GenericIPAddressField() first_visit = models.DateTimeField(default=timezone.now) last_visit = models.DateTimeField(default=timezone.now) visit_count = models.IntegerField(default=1) class Meta: indexes = [ models.Index(fields=['ip_address']), ] class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='Пользователь') phone = models.CharField(max_length=20, blank=True, verbose_name='Телефон') company = models.CharField(max_length=255, blank=True, verbose_name='Компания') specialization = models.CharField(max_length=100, blank=True, verbose_name='Специализация') avatar = models.ImageField(upload_to='avatars/%Y/%m/%d/', blank=True, verbose_name='Аватар') email_notifications = models.BooleanField(default=True, verbose_name='Получать уведомления') def __str__(self): return f'Профиль {self.user.username}' class Meta: verbose_name = 'Профиль' verbose_name_plural = 'Профили' @receiver(post_save, sender=User) def create_or_save_user_profile(sender, instance, created, **kwargs): if created: Profile.objects.get_or_create(user=instance) @receiver([post_save, post_delete], sender=Home) @receiver([post_save, post_delete], sender=Solution) @receiver([post_save, post_delete], sender=Competence) @receiver([post_save, post_delete], sender=Recall) def clear_sitemap_cache(sender, **kwargs): cache.delete('sitemap_cache')